Next.js 静态导出:那些你不知道的坑(附完美避坑方案)

Next.js 静态导出:那些你不知道的坑(附完美避坑方案)

为什么我明明用 Next.js 写得好好的,一开 output: 'export' 就报错?
为什么我的 API 代理在本地跑得飞起,部署到静态托管就 404?
为什么我的环境变量到了线上就变不回原来的值?

如果你也有这些困惑,恭喜你——你正在经历从“Next.js 新手”到“Next.js 老油条”的必经之路

我最近在做 FastAPI + Next.js 前后端分离项目,为了极致节约资源、随处部署,铁了心要用纯静态导出(output: 'export')。本以为 Next.js 这么成熟,静态导出就是 next build 一下,结果一路踩坑踩到怀疑人生。

今天就把我用真金白银的时间换来的经验整理出来,送给所有想用 Next.js 做纯静态网站的你。


💥 坑一:动态路由,静态导出的死敌

我的项目里有一个文章详情页,路由长这样:

app/article/[id]/page.tsx

很常见的动态路由对吧?然后我在 next.config.js 里加了:

output: 'export'

接着 npm run build —— 报错

Error: Page "/article/[id]" is missing "generateStaticParams()"
so it cannot be used with "output: export" config.

原因:静态导出必须在构建时就生成所有页面的 HTML。你给了一个动态参数 [id],Next.js 根本不知道有哪些 id,自然没法提前生成页面。


✅ 解法 1:generateStaticParams(适合文章数可控、构建时可访问 API)

在页面文件里加上:

export async function generateStaticParams() {
  const res = await fetch('https://api.example.com/articles')
  const articles = await res.json()
  return articles.map(article => ({ id: String(article.id) }))
}

这样构建时会自动生成 /article/1.html/article/2.html……完美。

但是——如果文章成千上万,或者你压根不想在构建时依赖后端 API,这就尴尬了。


✅ 解法 2:改用查询参数,彻底消灭动态路由(我的最终选择)

把路由改成:

app/article/page.tsx

然后用 ?id=xxx 传参:

'use client';
import { useSearchParams } from 'next/navigation';

const searchParams = useSearchParams();
const id = searchParams.get('id');
fetch(`/api/article/${id}`); // 或者完整后端地址

优点:零构建时依赖,纯静态文件,随便扔哪里都能跑。
代价:URL 从 /article/123 变成 /article?id=123 —— 如果你不在意 SEO,这根本不算代价。

我选这个,因为这个项目是内部工具,Google 爬虫进不来。
如果你要做公开内容站,建议评估一下 SEO 影响——但别被吓倒,Google 已经能抓 CSR 页面了


💥 坑二:rewrites 在静态导出下就是个摆设

为了让前端代码优雅地写 /api/v1/xxx,我在 next.config.js 里配了代理:

async rewrites() {
  return [
    {
      source: '/api/v1/:path*',
      destination: 'http://localhost:8000/api/v1/:path*',
    }
  ]
}

本地 npm run dev 爽歪歪,一打包部署到 Nginx,所有 API 请求全 404

为什么?
因为 rewritesNext.js 服务器的功能。当你 output: 'export' 后,产出的只是一堆 .html 文件,压根没有服务器在跑,自然没有任何代理规则生效。


✅ 解法 A:放弃幻想,前端直接写完整 URL

const API_BASE = 'https://api.example.com';
fetch(`${API_BASE}/api/v1/articles`);

配合运行时配置文件(见坑三),一套代码跑遍开发、测试、生产。


✅ 解法 B:如果你有 Nginx 控制权,在 Nginx 层代理

location /api/v1/ {
    proxy_pass http://your-fastapi:8000/api/v1/;
}

前端代码依然可以写 /api/v1/xxx,但部署环境必须配 Nginx,不能直接扔对象存储。


我选了解法 A,因为我要的是“能扔到任何静态托管平台”。


💥 坑三:NEXT_PUBLIC_* 环境变量,构建时硬编码

以前我都是这样用:

NEXT_PUBLIC_API_URL=https://api.example.com npm run build

代码里:

fetch(process.env.NEXT_PUBLIC_API_URL + '/users')

本地没问题,直到我需要同一份构建包部署到多个环境(开发、测试、生产)。
不行,因为 NEXT_PUBLIC_*next build 时就写死在 JS 文件里了,改不了。


✅ 终极方案:运行时配置文件

public 目录下放一个 config.js

window.__RUNTIME_CONFIG__ = {
  API_BASE_URL: 'http://localhost:8000'  // 默认值
}

app/layout.tsx 中引入:

<Script src="/config.js" strategy="beforeInteractive" />

代码里读取:

const baseUrl = typeof window !== 'undefined' 
  ? window.__RUNTIME_CONFIG__?.API_BASE_URL 
  : '';

部署时,直接修改服务器上的 config.js 文件,一行命令都不用重新构建。
支持任意静态托管(Nginx、S3、GitHub Pages),这才是真正的“一处编译,到处运行”


🧠 总结:静态导出,到底适合谁?

你的项目特点 是否适合纯静态导出 推荐方案
内容站,SEO 要求高,文章数少 ✅ 非常适合 generateStaticParams
内容站,文章成千上万 ⚠️ 谨慎评估 考虑 ISR 或 SSR
内部工具、后台管理 ✅ 极适合 查询参数 + 运行时配置
需要 Next.js API Routes ❌ 不适合 必须跑 Node.js 服务器

静态导出最纯粹的价值零运行时服务器、零构建时依赖、极低成本、极致弹性

为了实现这个价值,你需要放弃:

  • 动态路由(除非预生成)
  • 内置代理 / 重写
  • 构建时注入的环境变量

这些放弃,换来的是一份可以跑在任何地方的纯前端代码。


🎁 彩蛋:查询参数也能拥有“干净” URL

如果你既想用查询参数,又想让用户看到 /article/123 这样的漂亮路径,可以在 Nginx / Vercel / Netlify 层做重写

Nginx 示例:

location /article/ {
    rewrite ^/article/(\d+)$ /article?id=$1 last;
}

用户访问 /article/123 → Nginx 内部重写成 /article?id=123 → 你的静态页面依然能拿到参数。
代码完全不用改,完美。


✍️ 最后

Next.js 是个强大的框架,但它的静态导出模式有自己的一套游戏规则。别试图把服务端的功能硬塞进静态导出里——当你选择静态导出,就请彻底拥抱“纯静态”哲学

希望这篇文章能让你少踩几个坑。如果你也遇到过其他奇怪的静态导出问题,欢迎在评论区留言分享 👇


本文基于真实项目经验,技术栈为 Next.js + FastAPI。

posted @ 2026-02-13 17:43  Athenavi  阅读(52)  评论(0)    收藏  举报