Skip to content

Hashnode & Astro

Hashnode is Hosted CMS that allows you to create a blog or a publication. It is a great choice for developers who want to write and share their content without worrying about the infrastructure. This guide, will go over how to integrate Hashnode with your Astro project.

In this section, we’ll use the graphql-request project to bring your data into your Astro project.

To get started you will need to have the following:

  1. An Astro project - If you don’t have an Astro project yet, our Installation guide will get you up and running in no time.

  2. A Hashnode site - Its free to get started and create a Solo site on Hashnode. You can create a site by visiting Hashnode.

First, you will need to install the graphql-request package. This package is a minimal GraphQL client that works well with Astro.

Terminal window
npm install graphql-request

Making a blog with Astro and Hashnode

Section titled Making a blog with Astro and Hashnode

With the setup above, you are now able to create a blog that uses Hashnode as the CMS.

  1. A Hashnode Blog
  2. An Astro project integrated with the graphql-request package installed.

This example will create an index page that lists posts with links to dynamically-generated individual post pages.

You can fetch your site’s data with the graphql-request package.

First, create the client.ts & schema.ts files under a lib directory.

  • Directorysrc/
    • Directorylib/
      • client.ts
      • schema.ts
    • Directorypages/
      • index.astro
  • astro.config.mjs
  • package.json

Initialize an API instance with the GraphQLClient using the URL from your Hashnode Website.

src/lib/client.ts
import { gql, GraphQLClient } from "graphql-request";
import type { AllPostsData, PostData } from "./schema";
export const getClient = () => {
return new GraphQLClient("https://gql.hashnode.com")
}
const myHashnodeURL = "astroplayground.hashnode.dev";
export const getAllPosts = async () => {
const client = getClient();
const allPosts = await client.request<AllPostsData>(
gql`
query allPosts {
publication(host: "${myHashnodeURL}") {
title
posts(first: 20) {
pageInfo{
hasNextPage
endCursor
}
edges {
node {
author{
name
profilePicture
}
title
subtitle
brief
slug
coverImage {
url
}
tags {
name
slug
}
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}") {
post(slug: $slug) {
author{
name
profilePicture
}
publishedAt
title
subtitle
readTimeInMinutes
content{
html
}
tags {
name
slug
}
coverImage {
url
}
}
}
}
`,
{ slug: slug }
);
return data.publication.post;
};

And setup the schema file to define the shape of the data returned from the Hashnode API.

src/lib/schema.ts
import { z } from "astro/zod";
export const PostSchema = z.object({
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({
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({
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>

The page src/pages/index.astro will display a list of posts, each with a description and link to its own page.

  • Directorysrc/
  • Directorylib/
    • client.ts
    • schema.ts
  • Directorypages/
    • index.astro
  • astro.config.mjs
  • package.json

Import getAllPosts() in the Astro frontmatter to fetch the data from Hashnode.

src/pages/index.astro
---
import { getAllPosts } from '../lib/client';
const data = await getAllPosts();
const allPosts = data.publication.posts.edges;
---

Fetching via the graphql client returns an array of objects containing the properties for each post such as:

  • title - the title of the post
  • brief - the HTML rendering of the content of the post
  • coverImage.url - the source URL of the featured image of the post
  • slug - the slug of the post

Use the posts array returned from the fetch to display a list of blog posts on the page.

src/pages/index.astro
---
import { getAllPosts } from '../lib/client';
const data = await getAllPosts();
const posts = data.publication.posts.edges;
---
<html lang="en">
<head>
<title>Astro + Hashnode</title>
</head>
<body>
{
posts.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>

The page src/pages/post/[slug].astro dynamically generates a page for each post.

  • Directorysrc/
  • Directorylib/
    • client.ts
    • schema.ts
  • Directorypages/
    • index.astro
    • Directorypost/
      • [slug].astro
  • astro.config.mjs
  • package.json

Import getAllPosts() and getPost() in the Astro frontmatter to fetch the data from Hashnode and generate the page route for each post.

src/pages/post/[slug].astro
---
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);
---

Create the template for each page using the properties of each post object.

src/pages/post/[slug].astro
---
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={coverImage.url} alt={post.title} />
<h1>{post.title}</h1>
<p>{post.readTimeInMinutes} min read</p>
<Fragment set:html={post.content.html} />
</body>
</html>

To deploy your site visit our deployment guide and follow the instructions for your preferred hosting provider.

More CMS guides