How to deploy a blog site using astro + Hashnode

How to deploy a blog site using astro + Hashnode
Bharadwaj Palakurthy

Bharadwaj Palakurthy

4 min read

What Are Astro and Hashnode?

Hashnode is a hosted Content Management System (CMS) that allows you to create a blog or publication easily. It provides a user-friendly interface and powerful features to manage your content.

Astro is a modern static site generator that allows you to build fast websites using your favorite frameworks. It’s perfect for integrating with APIs, making it an excellent choice for our blogsite.

Prerequisites

Before we get started, make sure you have the following:

  1. An Astro Project: If you don’t have one yet, follow the Astro Installation Guide to get set up.

  2. A Hashnode Site: Create a free personal site by visiting Hashnode.

Step 1: Install Dependencies

First, we need to install the graphql-request package, which is a minimal GraphQL client that works seamlessly with Astro. Open your terminal and run:

npm install graphql-request

You can also use pnpm or Yarn if you prefer.

Step 2: Set Up Your Project Structure

Create the following directory structure in your Astro project:

src/
  lib/
    client.ts
    schema.ts
  pages/
    index.astro
    post/
      [slug].astro
astro.config.mjs
package.json

Step 3: Create the GraphQL Client

In src/lib/client.ts, initialize a GraphQL client to interact with the Hashnode API:

import { gql, GraphQLClient } from "graphql-request";
import type { AllPostsData, PostData } from "./schema";

export const getClient = () => {
  return new GraphQLClient("https://gql.hashnode.com");
}

const myHashnodeURL = "your-hashnode-url"; // Replace with your Hashnode URL

export const getAllPosts = async () => {
  const client = getClient();
  const allPosts = await client.request<AllPostsData>(
    gql`
      query allPosts {
        publication(host: "${myHashnodeURL}") {
          id
          title
          posts(first: 20) {
            pageInfo {
              hasNextPage
              endCursor
            }
            edges {
              node {
                id
                author {
                  name
                  profilePicture
                }
                title
                subtitle
                brief
                slug
                coverImage {
                  url
                }
                publishedAt
                readTimeInMinutes
              }
            }
          }
        }
      }
    `
  );

  return allPosts;
};

export const getPost = async (slug: string) => {
  const client = getClient();
  const data = await client.request<PostData>(
    gql`
      query postDetails($slug: String!) {
        publication(host: "${myHashnodeURL}") {
          id
          post(slug: $slug) {
            id
            author {
              name
              profilePicture
            }
            publishedAt
            title
            subtitle
            readTimeInMinutes
            content {
              html
            }
            tags {
              name
              slug
            }
            coverImage {
              url
            }
          }
        }
      }
    `,
    { slug }
  );

  return data.publication.post;
};

Step 4: Define the Data Schema

In src/lib/schema.ts, define the shape of the data returned from the Hashnode API:

import { z } from "astro/zod";

export const PostSchema = z.object({
    id: z.string(),
    author: z.object({
        name: z.string(),
        profilePicture: z.string(),
    }),
    publishedAt: z.string(),
    title: z.string(),
    subtitle: z.string(),
    brief: z.string(),
    slug: z.string(),
    readTimeInMinutes: z.number(),
    content: z.object({
        html: z.string(),
    }),
    tags: z.array(z.object({
        name: z.string(),
        slug: z.string(),
    })),
    coverImage: z.object({
        url: z.string(),
    }),
});

export const AllPostsDataSchema = z.object({
    id: z.string(),
    publication: z.object({
        title: z.string(),
        posts: z.object({
            pageInfo: z.object({
                hasNextPage: z.boolean(),
                endCursor: z.string(),
            }),
            edges: z.array(z.object({
                node: PostSchema,
            })),
        }),
    }),
});

export const PostDataSchema = z.object({
    id: z.string(),
    publication: z.object({
        title: z.string(),
        post: PostSchema,
    }),
});

export type Post = z.infer<typeof PostSchema>;
export type AllPostsData = z.infer<typeof AllPostsDataSchema>;
export type PostData = z.infer<typeof PostDataSchema>;

Step 5: Display a List of Posts

Now, let’s fetch and display the list of blog posts on the index page. In src/pages/index.astro, add the following code:

---
import { getAllPosts } from '../lib/client';

const data = await getAllPosts();
const allPosts = data.publication.posts.edges;
---

<html lang="en">
    <head>
        <title>Astro + Hashnode</title>
    </head>
    <body>
        {
            allPosts.map((post) => (
                <div>
                    <h2>{post.node.title}</h2>
                    <p>{post.node.brief}</p>
                    <img src={post.node.coverImage.url} alt={post.node.title} />
                    <a href={`/post/${post.node.slug}`}>Read more</a>
                </div>
            ))
        }
    </body>
</html>

Step 6: Generate Individual Post Pages

To create dynamic routes for each post, create a new file src/pages/post/[slug].astro and add the following code:

---
import { getAllPosts, getPost } from '../../lib/client';

export async function getStaticPaths() {
  const data = await getAllPosts();
  const allPosts = data.publication.posts.edges;
  return allPosts.map((post) => {
    return {
      params: { slug: post.node.slug },
    }
  });
}

const { slug } = Astro.params;
const post = await getPost(slug);
---

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>{post.title}</title>
    </head>
    <body>
        <img src={post.coverImage.url} alt={post.title} />
        <h1>{post.title}</h1>
        <p>{post.readTimeInMinutes} min read</p>
        <Fragment set:html={post.content.html} />
    </body>
</html>

Conclusion

Congratulations! You’ve successfully created a blogsite using Astro and Hashnode. You now have a dynamic blog that fetches posts from Hashnode and displays them on your site. You can customize the design and functionality further to suit your needs.

Feel free to share your blog with the world and happy blogging! If you have any questions or need further assistance, don’t hesitate to reach out.