Next.js 项目最容易让人误判的一点是:
本地运行正常,不等于部署以后也会正常。
开发环境下,Next.js 帮你兜掉了很多问题:
- 路由由开发服务器接管
- 图片处理能力默认可用
- 某些不稳定的双端差异不一定马上暴露
但一旦进入真实部署环境,尤其是静态导出场景,几个老问题会集中出现:
- Hydration Error
- 刷新子页面 404
<Image />相关构建或运行异常
这篇文章不讲抽象理论,重点讲三个最常见的坑,以及更稳的排查顺序。
先判断:你现在是哪一种部署方式
很多问题不是 Next.js 本身“坏了”,而是部署方式和项目能力不匹配。
通常先分成两类:
1. 运行在 Node 环境里的 Next.js
这种模式下,你保留了服务器能力,Next.js 可以继续处理:
- SSR
- 动态路由
- 图片优化
- 服务端逻辑
2. 静态导出后的纯静态站
也就是你配置了:
output: 'export'
这时最终上线的是一组静态文件,而不是完整的 Next.js 运行时。
后面的大多数坑,其实都和这条边界有关。
第一类常见问题:Hydration Error
最典型的报错通常像这样:
Text content does not match server-rendered HTML
Hydration failed because the initial UI does not match what was rendered on the server
它的本质是:
- 服务端先生成了一份 HTML
- 浏览器接管时又计算出另一份结果
- 两边不一致,React 只能报错
最容易踩的几种写法
1. 直接在渲染里读取时间
new Date().toLocaleTimeString()
服务器构建时的时间和用户打开页面时的时间不一样,这几乎注定会出问题。
2. 直接依赖 window、localStorage、浏览器尺寸
如果某段 UI 只有浏览器端才知道结果,而你又让它参与首屏渲染,就很容易产生双端差异。
3. 随机值直接参与渲染
比如:
Math.random()
crypto.randomUUID()
4. 条件分支依赖客户端状态
例如根据浏览器环境、主题、设备宽度直接决定初始 DOM 结构。
更稳的处理顺序
1. 先找“渲染时不稳定值”
不要一上来就关掉 StrictMode,也不要先压报错。
先排查这些内容是否直接出现在 JSX 渲染阶段:
- 时间
- 随机数
- 浏览器 API
- 客户端独有状态
2. 把客户端才知道的值延后到客户端处理
更常见的处理方式是:
- 放进
useEffect - 首屏先给稳定占位
- 等客户端挂载后再更新
3. 只在确实合理时用 suppressHydrationWarning
例如时间戳、相对时间、某些你明确知道双端必然不一致的文本。
像这样:
<div suppressHydrationWarning>{new Date().toLocaleTimeString()}</div>
但这不是通用解法,更像是“我确认这里允许差异”的局部豁免。
一个实用判断标准
如果某段内容在构建时和用户打开页面时很可能不同,那它就不应该无条件参与首屏一致性要求。
第二类常见问题:静态导出后刷新 404
这是 Next.js 静态导出里非常常见的问题。
你本地访问:
/blog/hello-world
开发服务器知道怎么匹配动态路由。
但你把站点导成静态文件以后,实际线上只是一堆:
.html.js.css
这时候如果分发层没有正确处理路由,请求刷新后很可能找不到对应文件。
为什么本地没问题,线上有问题
因为本地有 Next.js 开发服务器帮你兜底,线上静态托管环境只会按文件路径找资源。
这也是为什么很多人会在部署后遇到:
- 首页正常
- 点击进入内页正常
- 一刷新内页就 404
更稳的处理方式
1. 先确认你是否真的适合 output: 'export'
如果你的项目强依赖:
- 动态服务端逻辑
- 服务端鉴权
- 运行时数据处理
那纯静态导出本来就可能不适合。
2. 静态托管环境要正确处理 clean URLs 和 fallback
这不是“Next.js 配置一下就一定解决”的问题,还取决于你的分发层怎么找文件。
在上码当前的静态分发链路里,分发层会结合项目版本清单和 clean URLs 规则去解析目标文件,这也是为什么静态导出场景下的路径访问会更稳定一些,而不是简单把所有路径都丢给一个最原始的文件服务器。
如果你想看更基础的静态上线逻辑,可以配合这篇:HTML 文件怎么变成网址?。
3. 每次部署后至少测这几条路径
/- 一个典型内容页
- 一个多级子路径页
- 浏览器直接刷新
- 手机端直接打开链接
不要只测首页。
第三类常见问题:<Image /> 在静态导出下不好用
很多人喜欢 Next.js 的 <Image />,原因也很合理:
- 自动优化
- 自动尺寸处理
- 更好的性能分数
但它默认依赖的是服务端图片处理能力。
如果你把项目导成纯静态站,这层能力就不一定还存在。
典型表现
- 构建报错
- 图片地址不正确
- 运行时图片无法正常加载
最该先确认的事
1. 你是不是静态导出模式
如果是,那就要重新审视图片策略,不要默认 <Image /> 的所有能力都还能原样工作。
2. 你到底想保留什么
很多项目真正需要的是:
- 图片能正常显示
- 不拖慢首屏
- 部署后路径稳定
这不一定意味着必须保留原生的服务端图片处理路径。
两种常见处理路线
路线 1:接受静态导出约束,降低图片能力
在一些场景下,开发者会改成更保守的静态策略,让图片至少稳定可用。
路线 2:把图片优化能力交给 CDN 或边缘层
这也是更适合静态托管的方向。
前端只负责生成合理的 URL,请求真正的裁剪、压缩和缓存交给分发层处理。
这样做的好处是:
- 更符合静态站部署方式
- 不要求你继续保留完整 Node 图片服务
- 更容易和缓存策略配合
一个更稳的部署检查单
如果你不想每次都靠猜,Next.js 项目上线前至少检查这些点:
| 检查项 | 为什么重要 |
|---|---|
| 首屏是否有时间、随机值、浏览器状态参与渲染 | 避免 Hydration Error |
| 是否真的适合静态导出 | 避免把不适合的项目硬导出 |
| 刷新子路径会不会 404 | 避免路由在真实访问中断掉 |
| 图片策略是否和部署方式一致 | 避免 <Image /> 相关问题 |
| 手机端和桌面端都测过 | 部署问题不只发生在桌面浏览器 |
如果你要发的是静态产物,什么路径更轻
如果你的目标不是保留完整 Next.js 运行时,而是发布构建后的静态产物,那么更适合的做法通常是:
- 在本地完成 build
- 确认导出目录
- 把最终静态产物部署到适合静态分发的环境
这类路径的重点不是“让服务器继续承担构建”,而是“让分发层稳定地接住静态结果”。这和上码现在的部署认知是一致的:尽量把重构建留在发布前,把线上侧重点放在版本分发、缓存与访问稳定性上。
最后的建议
Next.js 项目部署后出问题,很多时候不是单个配置项错了,而是三件事没有对齐:
- 渲染方式
- 导出方式
- 分发方式
你只要把这条链路理顺,绝大多数“本地没问题、线上全出错”的问题都能更快定位。
先问自己这三个问题:
- 这段 UI 首屏是否要求双端完全一致?
- 这个项目是否真的适合静态导出?
- 现在的分发环境是否理解 Next.js 导出后的路径和资源行为?
把这三件事讲清楚,部署就会从“玄学”变成可排查的问题。