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.
Before we get started, make sure you have the following:
An Astro Project: If you don’t have one yet, follow the Astro Installation Guide to get set up.
A Hashnode Site: Create a free personal site by visiting Hashnode.
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.
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
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;
};
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>;
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>
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>
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.