秋实-Allenyou 的小窝

稻花香里说丰年,听取蛙声一片

【记录】我是如何用 Next.js 重构我的博客的

2024/3/9

前段时间完成了博客的重构,在这里记录一些重构过程中的经验(?)

技术选型

静态 vs 动态

从 2019 年开始,我的博客一直使用 Wordpress/Typecho 等基于 PHP 的 CMS 构建。

动态生成站点内容的好处是显而易见的,比如使用体验良好的管理后台、自带的评论系统、更快的更新站点速度等。此外,相比于需要自行在本地搭建开发环境的 Hexo 等静态博客系统,动态 CMS 更加”开箱即用“的设置也极大降低了搭建博客的门槛。

但是相对的,Wordpress/Typecho 等动态 CMS 的问题也很明显:每次打开页面都需要访问数据库带来的较高 TTFB、与 PHP 深度耦合导致无法使用 React/Vue 等相对更”现代“的前端技术栈……虽然使用 Redis/Memcached 缓存 + 静态资源 CDN 的方案可以缓解,但静态 CMS 方案在 TTFB 上的优势依旧是显而易见的。

考虑到我的服务器基本都是 2C2G 这种入门配置,还是用静态方案吧(

前端:React or Vue?

目前主流的前端框架主要有 React 和 Vue 两大派(其他后起之秀我不了解,这次显然没法用),两个框架提供的特性、性能都大差不差,所以选择哪一个主要取决于上层框架。

在这点上,同样是面向 SSR 的上层框架,基于 React 的 Next.js 拥有远好于基于 Vue 的 Nuxt.js 的生态,出各种问题都能更好地找到资料。

最后权衡一下,选择了 React & Next.js 的前端方案。

CSS:Tailwind CSS 真香

首先声明,我对前端设计一窍不通

因为没有正统学习过前端 HTML + CSS + JS 技术栈,我一直都是以”现用现学“的方法补习这些基础知识。但是同样地,现在让我自己用传统的 CSS/SCSS 去构建博客的前端样式显然会堆成屎山。此外,CSS 优化这个老生常谈的问题显然能直接让我原地爆炸

好在有 Tailwind CSS。

你说得对,但是 Tailwind CSS 是由 Tailwind Labs 自主研发的一款全新 Utilities-first CSS 框架。

正经点说,Tailwind CSS 把原有的众多 CSS 规则全部拆成了 class 的形式,使用时不需要写 CSS,只要直接写 class 就行,每条 className 对应一条简单的规则,比如 p-4 对应 padding: 1rem 规则,在最终构建时 Tailwind CSS Compiler 将扫描代码,提取所有用到的规则名称并且合并为一个 CSS 文件。

更多详情请前往官网

后端:Not Hexo

虽然 Hexo 的 Warehouse 后端用的人很多,但是我这里没有用这个。

一方面是我的博客去除了分类/标签,而 Hexo 有这个,略显麻烦;另一方面我太菜没搞明白文档

所以我写了个简单的后端,使用 frontmatter 库在 .md 文件头写标题、日期之类的 Metadata,通过扫描文件建立临时 CMS。

源代码在 src/libs/content.ts 里面。

技术笔记

图片 Lazyload & 灯箱

这里图片的 Lazyload 优化使用了 Sukka 大佬的实现,代码在这里

同时,我在 <LazyloadImage /> 组件中附加了 medium-zoom 库,添加了图片点击放大的效果。

为了让 Lazyload 和 medium-zoom 的 Ref 不冲突,我将原本官方的 RefCallback 改成了这样:

const myref: RefCallback<HTMLImageElement> = (node) => {
	imageElRef.current = node;
	const zoom = getZoom();
	if (node) {
		zoom.attach(node);
	} else {
		zoom.detach();
	}
};

扩展 Markdown 解析

参照 Sukka 大佬的思路,我在博客构建时通过 markdown-it 库对 Markdown 内容进行解析渲染为 HTML,并通过 htmr 库将生成的 HTML 解析为 React 节点树插入页面。在此过程中,我通过 htmr 库的 transform 参数控制了 HTML 标签的解析方式,将 a 标签解析为自定义的 <AllenyouLink /> 组件,从而实现内外链区分跳转(博客内链在当前页面无刷新跳转,外链使用 _blank 参数在新页面打开),同时将 img 标签解析为 <LazyloadImage /> 组件实现 Lazyload。

同时,由于 Markdown 原生支持内嵌 HTML,在 Markdown 源文件中插入的 HTML 代码会被原样保留。因此,综合考虑后,我对 div 标签的解析做了修改,替换为自定义的 <AllenyouDiv /> 组件。

在此组件中,我添加了属性 component。当 component 为空或不在预先设定的列表中时,将原样解析为 div 标签。若 component 值在预定义的列表中时,将解析为自定义的 React 组件。

目前已经实现的组件有:

  • Bilibili: 嵌入 Bilibili 视频播放器

相关组件在 src/components/MarkdownExtend 下存放。

// src/components/AllenyouDiv.tsx
export default function AllenyouDiv(
	props: JSX.IntrinsicElements["div"] & {
		component?: string;
		children?: React.ReactNode;
	}
) {
	if (props === undefined || !props.hasOwnProperty("component")) {
		return <div>{props.children}</div>;
	}
	if (!customComponents.hasOwnProperty(props.component!)) {
		return <FallbackComponent {...props}>{props.children}</FallbackComponent>;
	}
	const Target = customComponents[props.component!];
	if (props.children === undefined || props.children === null) {
		return <Target {...props} />;
	} else {
		return <Target {...props}>{props.children}</Target>;
	}
}

博客的 CI 构建

在博客的构建上,我选择在本地构建,通过 npm 的 postbuild 钩子将构建好的网站提交到服务器上的 Git 仓库,并在服务器上使用 Git Hook 搭建简单的 CI 实现自动将网站部署到 NGINX 网站目录下,并将静态文件上传到 OSS,刷新 CDN 缓存。

详情请参见这篇文章

未完待续

【记录】我是如何用 Next.js 重构我的博客的

https://www.allenyou.wang/post/16

本文作者

秋实-Allenyou

授权协议

CC BY-NC-SA 4.0

加载评论中……