Skip to content

Hashnode 和 Astro

Hashnode 是一个托管 CMS,可让你创建博客或发布物。

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

¥Integrating with Astro

Hashnode 公共 API 是一个 GraphQL API,允许你与 Hashnode 交互。本指南使用 graphql-request(一个与 Astro 配合良好的最小 GraphQL 客户端)将你的 Hashnode 数据带入你的 Astro 项目。

¥The Hashnode Public API is a GraphQL API that allows you to interact with Hashnode. This guide uses graphql-request, a minimal GraphQL client that works well with Astro, to bring your Hashnode data into your Astro project.

¥Prerequisites

要开始使用,你需要具备以下条件:

¥To get started you will need to have the following:

  1. Astro 项目 - 如果你还没有 Astro 项目,我们的 安装指南 将立即帮助你启动并运行。

  2. Hashnode 网站 - 你可以通过访问 Hashnode 创建免费的个人网站。

¥Installing dependencies

使用你选择的包管理器安装 graphql-request 包:

¥Install the graphql-request package using the package manager of your choice:

终端窗口
npm install graphql-request

使用 Astro 和 Hashnode 制作博客

标题部分 使用 Astro 和 Hashnode 制作博客

¥Making a blog with Astro and Hashnode

本指南使用 graphql-request(一个与 Astro 配合良好的最小 GraphQL 客户端)将你的 Hashnode 数据带入你的 Astro 项目。

¥This guide uses graphql-request, a minimal GraphQL client that works well with Astro, to bring your Hashnode data into your Astro project.

¥Prerequisites

  1. Hashnode 博客
  2. 一个安装了 graphql-request 包的 Astro 项目。

此示例将创建一个索引页面,其中列出帖子以及动态生成的各个帖子页面的链接。

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

¥Fetching Data

  1. To fetch your site’s data with the graphql-request package, make a src/lib directory and create two new files client.ts & schema.ts:

    • Directorysrc/
      • Directorylib/
        • client.ts
        • schema.ts
      • Directorypages/
        • index.astro
    • astro.config.mjs
    • package.json
  2. 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}") {
    id
    title
    posts(first: 20) {
    pageInfo{
    hasNextPage
    endCursor
    }
    edges {
    node {
    id
    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}") {
    id
    post(slug: $slug) {
    id
    author{
    name
    profilePicture
    }
    publishedAt
    title
    subtitle
    readTimeInMinutes
    content{
    html
    }
    tags {
    name
    slug
    }
    coverImage {
    url
    }
    }
    }
    }
    `,
    { slug: slug }
    );
    return data.publication.post;
    };
  3. Configure schema.ts 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({
    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>

¥Displaying a list of posts

通过 getAllPosts() 获取将返回一个对象数组,其中包含每个帖子的属性,例如:

¥Fetching via getAllPosts() returns an array of objects containing the properties for each post such as:

  • title - 帖子的标题

  • brief - 帖子内容的 HTML 渲染

  • coverImage.url - 帖子特性图片的源 URL

  • slug - 帖子的标题

使用从获取返回的 posts 数组来显示页面上的博客文章列表。

¥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>

¥Generating pages

  1. Create the page src/pages/post/[slug].astro to dynamically generate a page for each post.

    • Directorysrc/
    • Directorylib/
      • client.ts
      • schema.ts
      • Directorypages/
        • index.astro
        • Directorypost/
          • [slug].astro
    • astro.config.mjs
    • package.json
  2. Import and use getAllPosts() and getPost() to fetch the data from Hashnode and generate individual page routes 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);
    ---
  3. Create the template for each page using the properties of each post object. The example below shows the post title and reading time, then the full post content:

    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>

¥Publishing your site

要部署你的网站,请访问我们的 部署指南 并按照你首选托管提供商的说明进行操作。

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

¥Community Resources

更多 CMS 指南

Astro 中文网 - 粤ICP备13048890号