【记录】我是如何用 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 缓存。
详情请参见这篇文章。
未完待续
加载评论中……