鸡排的首页和博客现已基于 Astro 重新呈现
最近看博客很不爽😕,想翻新一下前端,本来叮叮当当搞了一个 Nextjs + Hexo 的组合,但是后来发现还是很麻烦,最后连 Hexo 也删掉,终于舒服了。
谈谈为什么要换
在上一次重构的时候,我的博客从 WordPress 换到了 Hexo,主要动机源于 我不想写 PHP 主题。然后我搬到 Hexo
,写了一个基于 Pug 和 Stylus 的主题,而在前端生态又蓬勃发展了几年后,这种糟糕的 DX 已经不再能满足我了。为什么我不能用 JSX 和 Tailwind 来写我的博客主题呢?
Next.js 很好,但有人拖后腿
在这篇文章发布前,我的博客其实已经由 Next.js + Hexo 的方案试运行了几个月时间。但我依然不是满意,主要原因在以下几个方面:
-
Hexo 内置的
WareHouse
JSON 数据库和相关 API 使用体验不佳。 -
本地编写草稿时还要处理 Hexo 自身的文件缓存、Next.js 的刷新等等问题。
-
Hexo API 给出的实例上的很多属性其实是个 getter,需要显式调用一下。
上面提到的一些问题,你都可以通过学习(抄) SukkaW 的代码来解决,我目测全网所有的 Next.js + Hexo 这个标题的文章里对于 Hexo
初始化和数据处理的 lib 都是来自 SukkaW 文章里的那份。
可以说 SukkaW 的这两篇文章是真正的开山鼻祖了: (要是没了他我都不知道该怎么办)
这之后我丢着博客一段时间没管,也算是冷静思考了一下,然后我问了自己一个问题:“留着 Hexo 的用途是什么?”
答案是显而易见的:前端渲染的工作已经交由别处,而内容本身就作为 markdown 而存在,我所需要的已经都有了,Hexo 对于现在的我来说就是没有意义的,我只需要让前端框架直接读 markdown 就行了。
你好 Astro
在作出移除 Hexo 的决定后,我现在需要我的框架来直接读取 markdown 作为数据,其实 Next.js 也支持这么干,但是隔壁 Astro 的以下特性实在太吸引我了:
- 提供内容集合的简单处理 API
- 自带分页功能的动态路由
- 官方支持的 RSS 和 Sitemap 生成
- 对 视图过渡动画 (View Transitions) 的官方支持
- 由 Shiki 支持的 Markdown 代码高亮
我认为这些特性都是博客场景下的重点需求,Next.js 并非做不到以上说的任何一点,但是 Astro 开箱即用。
比如自带分页功能的动态路由,你把数据集和PageSize
喂给它,Astro 会将每一页的数据和常见的Pagination
属性都作为页面的Props
传入,不需要开发者再自行处理数据切分相关的逻辑。
还有 视图过渡动画,对于 Next.js 这样的全栈框架来说支持起来也许很困难,就像这个 discussions 一样: Add support for View Transition API #46300,但是对于 Astro 而言,两行代码就可以启用支持。
剩下的 代码高亮、RSS、Sitemap 功能虽然都是选配,但是作为官方内置支持功能可以在你需要的时候轻松集成,不需要第三方社区插件和长篇大论的独立配置文件。
就此时此刻而言,我认为如果你的需求是几乎没有 JS 部分的纯内容网站,那么 Astro 就是你现在能买到的最好的现代前端化的 SSG 框架。
开工
起步
强烈推荐使用 Astro 的 cli 脚手架创建带有博客模版的项目,不仅附带填充数据,RSS 和 Sitemap 都已启用,只需要按需微调。
定义博客 Schema
首先删除./src/content/blog/
中的预填充文章,将你的文章都复制到这个目录下,有子目录也没有关系。
然后按需修改 ./src/content/config.ts
中的 schema 定义,对于用过 Zod
或者类似校验库的朋友们来说简直不能再熟悉了。
const blog = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string().optional(),
pubDate: z.coerce.date().optional(),
updatedDate: z.coerce.date().optional(),
// 这些都是 Hexo 的老朋友
date: z.coerce.date(),
permalink: z.string(),
categories: z.array(z.string()).optional().nullable(),
tags: z.array(z.string()).optional().nullable(),
photos: z.array(z.string()).optional().nullable(),
draft: z.boolean().optional(),
}),
});
不出意外地话,你现在可以使用 getCollection("blog")
方法来获取你的博客数据了。
分页
Astro 内置非常强大的分页功能支持,以最简单的文章列表分页为例:
假定列表页面都位于 page/
目录下,新建一个 src/pages/page/[page].astro
:
export async function getStaticPaths({
paginate,
}: {
paginate: PaginateFunction;
}) {
const posts = await getCollection("blog");
return paginate(posts, { pageSize: 10 });
}
// 所有分页数据都在 "page" 参数中传递
const { page } = Astro.props;
console.log(page.data.length, page.data);
现在 page.data
就是一个长度为 10 的文章数组,直接在下方的模版中使用这个变量 Map 生成文章卡片即可。对于分页器而言,可以直接使用 page.url.next
和 page.url.prev
,如果你还有更多需求,这是Page
的类型说明:
export interface Page<T = any> {
/** result */
data: T[];
/** metadata */
/** the count of the first item on the page, starting from 0 */
start: number;
/** the count of the last item on the page, starting from 0 */
end: number;
/** total number of results */
total: number;
/** the current page number, starting from 1 */
currentPage: number;
/** number of items per page (default: 25) */
size: number;
/** number of last page */
lastPage: number;
url: {
/** url of the current page */
current: string;
/** url of the previous page (if there is one) */
prev: string | undefined;
/** url of the next page (if there is one) */
next: string | undefined;
};
}
Markdown 样式
自己处理 Markdown 样式太痛苦了,你可以使用 Tailwind 的预设 Markdown 样式: tailwindcss-typography再简单微调一下,就可以得到非常不错的效果!
具体的步骤可以直接看官方文档:recipes/Tailwind Typography
代码高亮
自带的代码高亮主题好像有点丑,在这里挑一个你喜欢的然后在配置文件里换上:
// astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
markdown: {
shikiConfig: {
theme: "one-light",
},
},
});
RSS 和 Sitemap
RSS 和 Sitemap 已经默认开启,因此你只需要修改 astro.config.mjs
中的 site url 和 consts
中的网站名称和描述即可。
如果你也想要一个像点我一样好看的 rss.xml 样式,在这里下载文件并放到 public/rss/styles.xml
。
// rss.xml.js
export async function GET(context) {
const posts = await getAllPost();
return rss({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
site: context.site,
stylesheet: "/rss/styles.xsl",
items: posts.map((post) => ({
title: post.data.title,
pubDate: post.data.date,
// 这里我禁用了 RSS 里的 description 和 customData 生成,修改了 link 的路径
// description: post.data.description,
// customData: post.data.customData,
link: `/${post.data.permalink}`,
})),
});
}
视图过渡
只需两行,请在你的 Layout.astro
中导入并使用即可:
import { ViewTransitions } from "astro:transitions";
<html lang="en">
<head>
...
<ViewTransitions />
</head>
<body>
</body>
</html>
最后
如果你还需要更多的动态路由页面,比如 Tag 和 Category,这两种一般是嵌套分页,可以参阅文档:嵌套分页
你还可以给自己加一个 404 报错页面: 404 页面
致谢
本文数次修改,早期成稿时曾参考: