跳到正文
花城
返回文章列表

你好,世界:这个博客是怎么搭起来的

从毛坯房到能住人——记录一次用 Next.js 静态导出 + TinaCMS + Cloudflare Pages 搭个人博客的完整过程,以及为什么最后选了纯静态。

全文 1.2k 字作者:花城浏览与点赞统计加载中

为什么又开一个博客#

写作这件事,我断断续续试过好几次。前面几次都死在同一件事上:发布太麻烦。

Markdown 写完了还要 git add、commit、push,然后等 CI 跑完。兴致来的时候这不算什么, 但更多时候我只是想改一个错别字——为了改错别字走一遍完整的部署流程,热情就消磨掉了。

所以这次的目标很明确,一共四条:

  • 网页后台直接写作,手机上也能改
  • 国内访问要快
  • 托管必须免费
  • 源码可以公开

这四条其实互相打架。网页后台意味着要有服务端,服务端就意味着不能纯静态、不能白嫖 CDN。 转折点是 TinaCMS:它把「后台」做成了纯前端应用,编辑时直接调 GitHub API 提交, 不需要我维护任何服务器。

技术选型#

最后定下来的组合是这样:

层级技术作用
框架Next.js 16App Router + 静态导出
UIReact 19组件化开发
样式Tailwind CSS 4原子化 CSS
内容MDXMarkdown 里嵌 React 组件
后台TinaCMS网页编辑器,保存即提交 GitHub
托管Cloudflare Pages全球 CDN,国内速度尚可

为什么不是 Vercel

Vercel 的体验确实好,但它的默认域名和部分 CDN 节点在国内访问不稳定。 Cloudflare Pages 免费额度更大,国内实测也更稳一些。 代价是构建产物必须是纯静态的,不能用 Server Actions、ISR 这些东西。

静态导出到底做了什么#

next build 执行的时候,Next.js 会把每个路由都渲染成一份 HTML 文件。

import createMDX from "@next/mdx";
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "export",        // 关键:生成静态 HTML
  images: { unoptimized: true },  // 静态导出必须关掉默认图片优化
  trailingSlash: true,     // 生成 posts/hello-world/index.html
};

export default createMDX({ options: { remarkPlugins: ["remark-gfm"] } })(nextConfig);

构建完之后,out/ 目录里就是一棵可以直接扔给任何静态服务器的文件树:

out/
├── index.html
├── posts/
│   ├── index.html
│   └── hello-world/
│       └── index.html
└── _next/static/…

访客打开页面时,服务器(其实是 CDN 边缘节点)只是把已经生成好的文件发出去, 不执行任何代码、不查任何数据库。这就是它快的原因,也是它便宜的原因。

静态导出的边界

一旦选了 output: "export",下面这些能力就用不了了: Server Actions、请求相关的 Route Handler、cookies()、ISR、默认的图片优化、 以及任何的 rewrites / redirects / headers 配置。这些都需要一个常驻的 Node 进程。

视频与音乐#

国内写博客绕不开一个问题:YouTube 打不开。所以视频统一走 B 站的 iframe 嵌入, 播放器直接连接 B 站的 CDN,我这边不需要承担任何流量。

<BilibiliVideo bvid="BV1xx411c7mD" title="示例视频" />

渲染出来是这样:

示例视频 · 把 bvid 换成你自己的 · 视频来自 Bilibili

音乐同理,用原生 <audio> 就够了。侧栏那个迷你播放器读的是 src/lib/music.ts 里的歌单,把 mp3 丢进 public/uploads/ 再登记一条就行。

示例曲目一 · 琶音

0:00 / 0:00

本站生成

这只是占位音频

上面这条指向 public/uploads/demo-01.wav,是仓库里自带的一段几秒钟的示例音, 用来让播放器「点开就有声音」。

换成你自己的音频:把文件放进 public/uploads/,再把 src 改成对应路径即可。 注意路径要对得上——指向不存在的文件时,浏览器控制台会看到一条 404。

写作时的完整链路#

打开 /admin → TinaCMS 编辑器 → 写文章 → 保存
     ↓
调用 GitHub API 提交到仓库
     ↓
Cloudflare Pages 检测到更新 → 重新构建 → 部署到 CDN
     ↓
大约 1 分钟后线上生效

整个过程中我唯一需要做的事情就是「写字」和「点保存」。这才是一个博客应该有的摩擦力。

后来补上的两件事

写这篇文章时评论和搜索都还没做,现在都已经落地了: 搜索是构建期生成一份静态索引,浏览器本地匹配(不需要 Pagefind); 评论则是三条通道——本机评论(零配置)、仓库里自带的互动服务 (Cloudflare Worker + KV,部署后所有人可见)、以及可选的 Giscus。 两者的共同点是:都不需要常驻服务器,符合这个站点的定位。

小结#

如果把建站比作装修,那大概是这样分工的:

  • .html 是承重墙,决定房子里有什么
  • .css 是装修,决定好不好看
  • .js 是电器开关,决定能不能互动
  • .mdx 是我的家具说明书——写清楚每件东西怎么用

这套组合没有哪一项是新技术,但拼在一起刚好满足了我那四条互相打架的需求。 如果你也想搭一个类似的博客,直接从仓库 clone 下来改就行,源码本来就是公开的。

评论0

0/1000

还没有评论,来写第一条吧。

访客的评论先存在你自己的浏览器里(纯静态站没有收件箱), 只有站长发布的留言与回复是写进仓库、所有人都能看到的;点赞也只记在本机。

想要「所有访客都看得见、站长能回复」的评论区,两条路选一条:部署仓库里自带的互动服务 (workers/blog-api,见 README),或者配置 Giscus。 现在既没配也没部署,所以上面的评论只存在各自的浏览器里。

相关文章