跳转到内容

Hashnode & Astro

此内容尚不支持你的语言。

Hashnode is a hosted CMS that allows you to create a blog or publication.

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.

  • 文件夹src/
    • 文件夹lib/
      • client.ts
      • schema.ts
    • 文件夹pages/
      • 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.

  • 文件夹src/
  • 文件夹lib/
    • client.ts
    • schema.ts
  • 文件夹pages/
    • 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 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>

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

  • 文件夹src/
  • 文件夹lib/
    • client.ts
    • schema.ts
  • 文件夹pages/
    • index.astro
    • 文件夹post/
      • [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={post.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.

更多 CMS 指南