用 Next.js 静态导出把博客部署到 Cloudflare Pages

这篇文章写的是这个博客自己是怎么被搭起来的。选择的过程、关键的配置,以及几个「不踩一遍就不知道」的坑。

为什么是静态导出

博客是典型的读多写少场景:文章是构建时确定的,不需要服务端按请求渲染。静态导出的好处很直接:

  • 没有服务端运行时,也就没有冷启动、没有计费单元,Cloudflare Pages 免费额度基本用不完。
  • 每个页面就是一张 HTML,SEO 和首屏速度都占便宜。
  • 部署形态极简:next build 产出一个 out/ 目录,原样托管即可。

代价同样明确——服务端能力一概没有。下面这张表把边界划清楚:

能力 静态导出下 说明
动态路由 需要 generateStaticParams 构建期枚举全部路径
API / Route Handlers 仅 GET + force-static 可用来生成 feed.xml、sitemap
图片优化 默认不可用 unoptimized 或自定义 loader
ISR / Server Actions 不可用 构建期一次定型

三个关键配置

next.config.ts 只有几行,但每一行都有讲究:

import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  output: "export",
  trailingSlash: true,
  images: { unoptimized: true },
};
 
export default nextConfig;

trailingSlash: true 尤其重要。Cloudflare Pages 对目录有自己的一套规范化:foo/index.html 对应 /foo/。如果站内链接一会儿带斜杠一会儿不带,每个链接都会吃一次重定向,Search Console 里会冒出一堆「重定向」噪音。定死一个,全站一致。

不写 not-found 会怎样

这是最大的坑:Cloudflare Pages 检测不到顶层 404.html 时,会把站点当成 SPA,所有未匹配的路径都返回 200 和首页内容。

死链返回 200,对 SEO 是灾难性的——搜索引擎会把根本不存在的页面当成真页面收录。

Next.js 这边只要写一个 app/not-found.tsx,静态导出就会自动产出 out/404.html,问题解决。部署后记得验证:

curl -o /dev/null -w "%{http_code}" https://你的站.pages.dev/不存在的路径

预期返回 404,而不是 200

内容就是文件

文章本身是仓库里的 Markdown,frontmatter 只用了四个字段:

---
title: "标题"
date: "2026-08-18"
tags: ["标签"]
summary: "一句话摘要"
draft: false
---

draft: true 的文章会在构建期被整体剔除——不出现在首页、标签页、归档、RSS 和 sitemap 的任何一处。写一半的稿子可以放心地 push 上去。

收尾

到这里,一条「写完 → push → 自动部署」的闭环就通了。下面这张图是整体的数据流:

静态博客构建流程

剩下的都是锦上添花:代码高亮、评论、RSS。它们会在后续迭代里一一补上。

评论