Hashnode & Astro
Hashnode 是一个托管的 CMS,允许你创建博客或出版物。
与 Astro 集成
标题为“与 Astro 集成”的部分Hashnode 公共 API 是一个 GraphQL API,允许你与 Hashnode 进行交互。本指南使用 graphql-request
,这是一个与 Astro 配合良好的最小化 GraphQL 客户端,用于将你的 Hashnode 数据引入到你的 Astro 项目中。
先决条件
标题为“先决条件”的部分在开始之前,你需要准备好以下内容
安装依赖
标题为“安装依赖”的部分使用你选择的包管理器安装 graphql-request
包
npm install graphql-request
pnpm add graphql-request
yarn add graphql-request
使用 Astro 和 Hashnode 创建博客
标题为“使用 Astro 和 Hashnode 制作博客”的部分本指南使用 graphql-request
,这是一个与 Astro 配合良好的最小化 GraphQL 客户端,用于将你的 Hashnode 数据引入到你的 Astro 项目中。
先决条件
标题为“先决条件”的部分- 一个 Hashnode 博客
- 一个集成了 graphql-request 包的 Astro 项目。
此示例将创建一个索引页面,列出文章并链接到动态生成的单个文章页面。
获取数据
标题为“获取数据”的部分-
要使用
graphql-request
包获取站点数据,请创建一个src/lib
目录并创建两个新文件:client.ts
和schema.ts
目录src/
目录lib/
- client.ts
- schema.ts
目录pages/
- index.astro
- astro.config.mjs
- package.json
-
使用来自你的 Hashnode 网站的 URL,通过 GraphQLClient 初始化一个 API 实例。
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}") {idtitleposts(first: 20) {pageInfo{hasNextPageendCursor}edges {node {idauthor{nameprofilePicture}titlesubtitlebriefslugcoverImage {url}tags {nameslug}publishedAtreadTimeInMinutes}}}}}`);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}") {idpost(slug: $slug) {idauthor{nameprofilePicture}publishedAttitlesubtitlereadTimeInMinutescontent{html}tags {nameslug}coverImage {url}}}}`,{ slug: slug });return data.publication.post;}; -
配置
schema.ts
以定义从 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>
显示文章列表
标题为“显示文章列表”的部分通过 getAllPosts()
获取数据会返回一个对象数组,其中包含每篇文章的属性,例如
title
- 文章的标题brief
- 文章摘要的 HTML 渲染coverImage.url
- 文章特色图片的源 URLslug
- 文章的 slug(永久链接)
使用获取返回的 posts
数组在页面上显示博客文章列表。
---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>
生成页面
标题为“生成页面”的部分-
创建页面
src/pages/post/[slug].astro
来为每篇文章动态生成一个页面。目录src/
目录lib/
- client.ts
- schema.ts
目录pages/
- index.astro
目录post/
- [slug].astro
- astro.config.mjs
- package.json
-
导入并使用
getAllPosts()
和getPost()
从 Hashnode 获取数据,并为每篇文章生成单独的页面路由。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);--- -
使用每个
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);---<!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><Fragment />
是一个内置的 Astro 组件,它允许你避免不必要的包装元素。当从 CMS(例如 Hashnode 或 WordPress)获取 HTML 时,这尤其有用。
发布你的站点
标题为“发布你的站点”的部分要部署你的网站,请访问我们的部署指南,并按照你首选的托管提供商的说明进行操作。
社区资源
标题为“社区资源”的部分- GitHub 上的
astro-hashnode