Next.js 静态导出 + Cloudflare Pages 部署实战:我踩过的 8 个坑

|王启航|15 分钟

前端架构师,5 年 Next.js 开发经验,Cloudflare 生态深度用户,维护多个 Serverless 部署的 Next.js 项目

从 Vercel 迁移到 Cloudflare Pages:一个周末变三个月的故事



说起来你可能不信,我最初只是想省点钱。



我的网站 Free API Hub 之前一直部署在 Vercel 上,Next.js + Vercel 的组合确实丝滑,push 代码自动部署,预览环境、Edge Functions、图片优化一应俱全。但随着流量慢慢上来,Vercel 的账单也开始让我肉疼——Pro 计划 $20/月,图片优化按调用次数计费,Serverless Function 执行时间超了还要加钱。一个月下来,光部署费用就快 $50 了。



然后我听说了 Cloudflare Pages:500 次构建/月、无限带宽、无限请求,全部免费。我当时就想,这还等什么?



结果这一迁移,从一个周末的小项目变成了三个月的持续踩坑。8 个大坑,每一个都让我怀疑人生。今天把这些坑全部整理出来,如果你也在考虑 Next.js 静态导出 + Cloudflare Pages 部署,这篇文章能帮你省掉至少两个周末。



技术架构全景



先说说我最终的架构方案,这样后面讲坑的时候你才有上下文:




  • 框架:Next.js 15 App Router

  • 输出模式output: "export" 静态导出(SSG)

  • 部署平台:Cloudflare Pages + 自定义域名 524900.xyz

  • API 层:Cloudflare Pages Functions(/functions/ 目录)提供 Serverless API

  • i18n:支持 zh-CN 和 en-US 两种语言

  • 组件模式:Server Component + Client Component 分离



关键的 next.config 配置长这样:



// next.config.ts
const nextConfig = {
output: "export",
trailingSlash: true,
images: {
unoptimized: true,
},
};


看着很简单对吧?三行配置而已。但就是这三行配置,背后藏着 8 个坑。



坑 1:next/image 图片优化直接废了



现象



静态导出之后,所有用了 <Image> 组件的页面全部报错:Error: Image Optimization using the default loader is not compatible with output: 'export'。整个网站的图片全部裂开,一个都加载不出来。



原因



Next.js 的 <Image> 组件默认使用服务端图片优化,它会在请求时动态压缩、转换图片格式(比如转 WebP),这需要 Node.js 运行时。但 output: "export" 生成的是纯静态 HTML,没有服务端,图片优化自然就废了。



解决方案



在 next.config 里加 images: { unoptimized: true },禁用 Next.js 的图片优化。然后用其他方案替代:




  • 手动用 Sharp 或 Squoosh 提前压缩图片

  • 用 Cloudflare Image Resizing(需要付费计划)

  • 我最终选的方案:直接用原始图片 + 懒加载,配合 Cloudflare CDN 的自动缓存,实际体验差别不大



说实话这个坑不算意外,Next.js 官方文档写了,但很多人(包括我)都是先写代码再看文档,结果一导出全炸了。



坑 2:i18n 路由 404——这个坑让我差点放弃



现象



这是最让我崩溃的一个坑。网站支持中英文切换,我用了 Next.js 的 i18n 路由方案,中文页面路径是 /zh-CN/mcp/,英文是 /en-US/mcp/。但我在 sitemap 里写的是 /mcp/,因为我觉得用户不应该看到语言前缀。



结果呢?Google 爬虫按照 sitemap 抓取 /mcp/,返回 404。Google Search Console 里全是 404 错误,收录量断崖式下跌。我花了整整一个周末才搞明白为什么 Google 收录的全是 404。



原因



静态导出后,Next.js 不会自动处理 i18n 路由重定向。也就是说,/mcp/ 这个路径在静态文件系统里根本不存在,只有 /zh-CN/mcp//en-US/mcp/ 存在。Vercel 部署时 Next.js 的中间件会自动处理这个重定向,但 Cloudflare Pages 不会。



解决方案



在 public 目录下创建 _redirects 文件:



# public/_redirects
/mcp/ /zh-CN/mcp/ 308
/api/ /zh-CN/api/ 308
/ /zh-CN/ 308


Cloudflare Pages 会读取这个文件,在 CDN 层做重定向。308 是永久重定向,告诉搜索引擎这个页面永久迁移了。



但这里有个细节:如果你同时用了 _routes.json,要注意 _redirects 的优先级。我的 _routes.json 配置了 API 路由:



// public/_routes.json
{
"version": 1,
"include": ["/api/*"],
"exclude": []
}


这个配置告诉 Cloudflare Pages Functions 处理 /api/* 路径的请求。_redirects 和 _routes.json 的优先级是:_redirects 先执行,然后才是 Pages Functions。所以 /api/* 不会被 _redirects 拦截。



坑 3:trailingSlash 不一致导致 Google 收录大乱



现象



Google Search Console 显示同一个页面有两个 URL:/mcp/mcp/。而且更离谱的是,有时候返回 308 重定向,有时候返回 301,Google 把它们当成了两个不同的页面,权重被分散了。



原因



Next.js 默认不带尾斜杠,但 Cloudflare Pages 的静态文件服务默认带尾斜杠(因为 /mcp/index.html 对应 /mcp/)。两边的重定向行为不一致,就导致了 308 和 301 混乱。



解决方案



在 next.config 里统一设置 trailingSlash: true,这样 Next.js 生成的所有链接都会带尾斜杠,跟 Cloudflare Pages 的行为一致。同时在 _headers 文件里设置规范 URL:



# public/_headers
/zh-CN/*
X-Robots-Tag: index, follow
Cache-Control: public, max-age=3600, s-maxage=86400
/en-US/*
X-Robots-Tag: index, follow
Cache-Control: public, max-age=3600, s-maxage=86400


设置完之后,Google Search Console 里的重复页面问题慢慢消失了,大概两周后收录恢复正常。



坑 4:Cloudflare Pages Functions 的 TypeScript 编译问题



现象



我在 /functions/ 目录下写了 Pages Functions,用的 TypeScript。本地开发一切正常,但推到 Cloudflare Pages 构建时,Next.js 的构建过程会尝试编译 /functions/ 目录下的 TypeScript 文件,然后报一堆类型错误,构建直接失败。



原因



Next.js 的 TypeScript 编译器默认会扫描项目根目录下的所有 .ts.tsx 文件,包括 /functions/ 目录。但 Cloudflare Pages Functions 的运行时是 Worker,跟 Next.js 的运行时完全不同,类型定义也不一样。Next.js 编译它当然会出错。



解决方案



tsconfig.json 里排除 /functions/ 目录:



// tsconfig.json
{
"compilerOptions": {
// ... 其他配置
},
"exclude": ["node_modules", "functions"]
}


然后给 /functions/ 目录单独创建一个 tsconfig.json,使用 Cloudflare Worker 的类型定义。这样两边互不干扰。



坑 5:blog.ts 里的嵌套模板字符串报错



现象



这个坑比较小众,但如果你也在用类似的方式管理博客内容,一定会遇到。我在 blog.ts 里用模板字符串写文章内容,文章里又有代码示例,代码示例里又有模板字符串(比如 JavaScript 的模板字面量)。反引号套反引号,直接语法报错。



原因



JavaScript 的模板字符串里不能再嵌套未转义的反引号,这是语法限制。



解决方案



两种方法:一是用 ` 转义内部的反引号(但可读性很差),二是把代码示例里的模板字符串改用字符串拼接。我选了后者,虽然代码示例不够优雅,但至少不会报错。



// 原来的写法(会报错):
const url = "/api/" + "${id}"; // 模板字符串嵌套会报错

// 改成字符串拼接:
const url = "/api/" + id;


小坑,但当时排查了半天,因为报错信息只说"Unexpected token",没说是模板字符串嵌套的问题。



坑 6:Server Component 里用了 useState 导致 Hydration Mismatch



现象



网站的搜索功能突然不工作了。点击搜索按钮没反应,控制台报错:Hydration failed because the server rendered HTML didn't match the client。更诡异的是,这个 bug 只在生产环境出现,本地开发完全正常。



原因



Next.js 15 App Router 默认所有组件都是 Server Component。我在 page.tsx 里直接用了 useStateonClick,这些是客户端 API,不能在 Server Component 里使用。本地开发时 Next.js 做了兼容处理,但静态导出后这个兼容就没了。



解决方案



把交互逻辑拆到 Client Component 里:



// page.tsx (Server Component)
import SearchBox from './SearchBox';

export default function Page() {
return (
<div>
<h1>Free API Hub</h1>
<SearchBox /> {/* Client Component */}
</div>
);
}

// SearchBox.tsx (Client Component)
'use client';
import { useState } from 'react';

export default function SearchBox() {
const [query, setQuery] = useState('');
// ... 搜索逻辑
}


关键就是 'use client' 这个指令。App Router 下,只有标记了 'use client' 的组件才能使用 hooks 和事件处理器。这个拆分模式是 Next.js App Router 的核心设计,但如果你是从 Pages Router 迁移过来的,很容易忽略这一点。



坑 7:_redirects 文件被误删导致全站 404



现象



某天早上起来一看,网站全站 404。不是某个页面 404,是所有页面都 404。Google Search Console 的错误数从个位数飙到了几百。



原因



我在做代码清理的时候,把 public/_redirects 文件当成临时文件删了。因为静态导出后 public/ 目录下的文件会被直接复制到输出目录,而 _redirects 看起来不像是个重要文件——它没有扩展名,编辑器也不会高亮它。



解决方案



两个措施:一是在 .gitignore 里明确不忽略 public/_redirectspublic/_headerspublic/_routes.json 这些文件;二是在 CI 里加了个检查步骤,确保这些文件存在:



# CI 检查脚本
if [ ! -f "public/_redirects" ]; then
echo "ERROR: public/_redirects is missing!"
exit 1
fi
if [ ! -f "public/_headers" ]; then
echo "ERROR: public/_headers is missing!"
exit 1
fi


这个坑让我深刻认识到:Cloudflare Pages 的配置文件(_redirects、_headers、_routes.json)虽然不起眼,但它们是整个路由系统的命脉。删了它们,网站就等于废了。



坑 8:Cloudflare Pages 构建时 tsx 命令找不到



现象



我的 Pages Functions 用 TypeScript 写的,构建脚本里用 tsx 来运行。本地没问题,但 Cloudflare Pages 构建时总是报 tsx: command not found。我在 package.json 的 devDependencies 里加了 tsx,但每次 npm install 之后 tsx 就不见了。



原因



Cloudflare Pages 的构建环境会先执行 npm install,然后执行你指定的构建命令。但它的 npm install 默认不安装 devDependencies(生产模式)。而 tsx 是开发依赖,放在 devDependencies 里,所以构建时找不到。



解决方案



tsx 放到 dependencies 而不是 devDependencies。虽然不太规范,但 Cloudflare Pages 的构建环境就是这么设计的。或者更好的方案:不用 tsx,直接用 wrangler 来构建和部署 Pages Functions,wrangler 是 Cloudflare 自己的工具,原生支持 TypeScript。



# 用 wrangler 构建 Pages Functions
npx wrangler pages functions build --outdir dist/functions


后来我把整个 Functions 的构建流程都迁移到了 wrangler,再也没有 tsx 找不到的问题了。



性能优化成果



踩完 8 个坑之后,网站终于稳定运行了。来看看性能数据:




  • Lighthouse 评分:Performance 96,Accessibility 100,Best Practices 95,SEO 98

  • LCP(最大内容绘制):1.2s(Cloudflare CDN 全球加速)

  • CLS(累积布局偏移):0.02(字体预加载 + 尺寸占位)

  • FID(首次输入延迟):8ms(静态页面几乎没有 JS 执行开销)

  • PageSpeed Insights 移动端评分:92 分



说实话,静态导出 + CDN 的性能上限确实比 SSR 高。因为所有页面都是预生成的 HTML,用户访问时直接从最近的 CDN 节点返回,不需要服务端渲染。唯一的代价是构建时间稍长(全站 500+ 个 API 页面,构建大概 3 分钟),但 Cloudflare Pages 的 500 次构建/月完全够用。



成本分析:Cloudflare Pages vs Vercel



最后算一笔账,这也是我迁移的初衷:




  • Cloudflare Pages 免费额度:500 次构建/月、无限带宽、无限请求、100MB Functions/次调用

  • Vercel 免费额度:100 次构建/月、100GB 带宽/月、Serverless Function 10s 超时

  • Vercel Pro:$20/月,6000 分钟构建时间、1TB 带宽、Function 60s 超时



对于 Free API Hub 这个量级的网站,Cloudflare Pages 的免费额度完全够用,每月成本是 $0。而之前在 Vercel 上,因为图片优化和 Function 执行时间的额外计费,每月要花 $20-50。



当然,Cloudflare Pages 也有它的局限:不支持 Next.js 的 ISR(增量静态再生成)、不支持 Server Actions、图片优化需要付费计划。如果你的项目重度依赖这些功能,还是老老实实用 Vercel 吧。



技术选型建议



最后给正在纠结技术选型的朋友一些建议:




  • 选 Cloudflare Pages + 静态导出的场景:内容型网站、文档站、博客、API 目录(比如我的 Free API Hub)、营销页面。这些网站的内容更新频率低、交互简单,静态导出的性能优势最明显。

  • 还是用 Vercel 的场景:需要 ISR、需要 Server Actions、重度依赖 next/image 优化、需要预览部署的团队协作场景。

  • 别碰的场景:需要用户登录态的动态页面、实时数据面板、电商购物车——这些场景静态导出根本不合适,别为难自己。



Free API Hub (524900.xyz) 现在就跑在这个架构上,你可以直接访问体验一下效果。如果你也在做 Next.js 静态站点生成 SSG 的项目,希望这篇文章能帮你少踩几个坑。有问题欢迎在网站留言,我看到都会回复。



最后一句:技术选型没有银弹,只有最适合你项目的方案。但踩过的坑一定要记下来——不是为了证明自己多厉害,而是为了让后来的人少走弯路。

常见问题

Q:Next.js 静态导出 + Cloudflare Pages 部署实战:我踩过的 8 个坑的核心观点是什么?

本文深入探讨了Next.js、Cloudflare Pages、静态导出等相关内容,为开发者提供了实用的Next.js指导和建议。

Q:如何应用本文介绍的技术?

文章提供了详细的步骤说明和代码示例,你可以按照文中的指导逐步实践。同时建议结合自己的项目需求进行适当调整。

Q:Free API Hub还提供哪些相关资源?

Free API Hub收录了500+个免费API接口,你可以在API列表中找到各种实用的接口。同时我们的技术博客会持续更新更多开发教程和最佳实践。

相关关键词

Next.jsCloudflare Pages静态导出SSG部署实战App Router前端架构Next.js 静态导出 + Cloudflare Pages 部署实战:我踩过的 8 个坑教程Next.js 静态导出 + Cloudflare Pages 部署实战:我踩过的 8 个坑指南API教程API开发免费APIAPI接口开发者教程编程教程技术博客API最佳实践API性能优化API安全API集成