编辑模式 · 点击文字即可修改 · Ctrl+S 导出 再次按 E 或点击左上角退出
模块 4.6

把前端项目发布到公网

现代前端最后一站:选对上线方式,把它送上服务器

零到全栈 · 李勃老师 · 2026
动手之前,先做道选择题

一个 Next 项目,
上线有两条路

先把这两种讲清楚,再选定一种照着干。

上一节留的扣子

.next/ 为什么不能直接交给 Nginx?

它不是一个"网站文件夹"
是一堆构建半成品
CSS / JS 散落在别处,凑不成干净的网站根目录,还混着只有服务器看得懂的东西。
它是做给谁吃的
喂给 next start 那台服务
整包结构是按"常驻 Next 服务"的标准摆的,不是给 Nginx 直接发的。
先回忆一个概念

两类组件,送给浏览器的东西不一样

服务端组件(默认)
只送一份 HTML
在服务端 / build 时就把界面画成 HTML,浏览器拿到成品,连 JS 都不用送。
客户端组件("use client"
HTML + 一份 JS
要响应点击、改状态、播动画——这些只能在浏览器里跑,所以额外送一份 JS。
路 A · 常驻 Next 服务

每个请求,都在服务器上当场现画

用户 A 的浏览器
等待响应…
A 的专属页面 ✓
🧑 用户 A
① 请求 GET /
④ 返回 A 的 HTML
▣ 服务器 · next start
7×24 常驻的 Node 进程
② 按 A 的身份,组织内容
③ 现场渲染出 HTML 📄
1用户 A 向服务器发来请求 GET / 2服务器按 A 的身份,在卡片内部组织好该给他的内容。 3把这一版页面,现场渲染成 HTML。 4顺着连线,把 A 的专属 HTML 返回浏览器。
另一条路的开关

一行 output: "export"

// next.config.mjs const nextConfig = { output: "export", // ← 就加这一行 };

告诉 Next:我每一页 build 时都能画死,别给我留服务了,直接打包成一个干净的静态 out/ 文件夹。

于是有了两条路

A:常驻服务 · B:静态导出

路 A · 跑一个常驻 Next 服务
最强,但最重
能按每个请求当场现画最新数据;代价是一个 Node 进程 7×24 常驻,费服务器资源。
路 B · 静态导出一个 out/
又轻又稳
每页一个预渲染好的 .html,交给 Nginx 转发,服务器上不用常驻任何东西。
划清两条路的地盘

什么样的站,走哪条路?

走 B · 静态
页面 build 时就能定下来的:纯展示站、博客、文档,以及"静态前端 + 后端 API、数据靠浏览器现取"的一大类
走 A · 常驻服务
页面的 HTML 本身必须按每个请求当场拼出来:海量、又时时变、还要被收录的电商 / 新闻 / 社媒等"重内容"站。
一个特别容易误读的点

"内容是动态的"
"必须走 A"

登录注册、读数据库、实时数值——绝大多数都是"静态前端 + 后端 API"做的,照样走 B。

走 B 也能有动态内容

静态前端 + 后端 API:数据浏览器事后取

用户 A 的浏览器
情感分数 —
情感分数 0.86 ✓
🧑 用户 A
① 请求页面
② html / css / js
③ 请求数据
④ 返回数据
▣ 静态服务
Nginx
只发静态文件 out/
▣ 后端
API · Python
专算情感分数、拼音
1浏览器向 Nginx 请求页面。 2Nginx 返回静态文件 html / css / js 3页面加载后,浏览器再向后端 API 请求数据。 4API 返回数据,浏览器把它填进页面。
我们选 B

跟 4.4 部署比,只有两处不同

1
产物
dist/out/
靠项目里加一行 output: "export"
2
Nginx
root 指向 out/ + 一句 try_files
其余全是 4.4 的肌肉记忆:push → pull → install → build。
差别二 · Nginx 配置

root 指向 out/,再加一句 try_files

改之前 · 4.4(Vite dist
server { listen 80 default_server; root .../zero-to-tech/dist; index index.html; }
改之后 · 4.6(Next out
server { listen 80 default_server; root .../zero-to-tech/out; # dist → out index index.html; location / { try_files $uri $uri.html $uri/ =404; } }

try_files:先按 URL 找文件,找不到就自动补 .html 再找——/text-lab 就命中了 out/text-lab.html,不再 404。

这条路怎么走过来的

4.4 的痛 → 4.5 的解药 → 4.6 的落地

4.4
React 版上线,F12 一看——页面是浏览器临时画的,埋下三个毛病。
4.5
解药
Next.js 把每一页预渲染成真实 HTML,从根上拔掉病根,但还没部署。
4.6
落地
一行 output: "export" 导成 out/,Nginx 直接把静态资源发给浏览器。
以后每次发布

这套"拉代码 + 构建",可以交给 CI/CD

本地
commit / push
服务器
git pull
服务器
npm run build
Nginx
服务 out/

比自动化更有价值的,是你心里有这条链路:源码在哪、产物在哪、谁 build、谁服务。

那条没走的路 A,通向哪

路 A 再往深走,就是 Next 全栈

连数据库
服务端组件在服务器上跑,可以直接读库。
用密钥
代码不发到浏览器,可以安全地用上只有服务器才有的密钥。
写后端接口
同一个项目里直接写 app/api/...,前后端一把抓。
但我们这门课的选择

静态前端 + 独立后端,而不是 Next 全栈

前端归前端
静态 out/ · 走 B
React 可换成 Vue,怎么改都不影响后端。
后端归后端
Python 单独起一个服务
专算情感分数和拼音,两边靠 API 通话、各自独立部署。
为什么不顺势教 Next 全栈

三个原因

1
不利于建立工程思维
前后端绑在一个框架里;分开,初学者才容易建立"两个独立角色"的心智。
2
Python 后端生态更成熟
尤其是算法模型、数据分析服务,Python 比 Node 生态丰富得多。
3
RSC 还年轻,不够稳
"请求时现画"这套机制还在成长——看下一页。
一件真事

2025 年 12 月,服务端组件爆出满分级漏洞

CVSS 10.0 · 远程代码执行(RCE)
一个不用登录的 HTTP 请求,就能让常驻服务跑任意代码
披露后很快被大规模利用。我自己当时也中招——攻击者把勒索信发进了我服务器的家目录(所幸只是台"玩具"机,重装 + 升级安全版本了事)。

新技术好用,但"够不够稳"得自己掂量。我们这门课,到"纯静态 out/ + 后端 API"为止——这已是行业主流,足够做出可靠的真东西。

现代前端,到此收工

下一模块,
后端登场

把 data/site.js 里的硬编码、那个"开始分析"按钮,换成真正的接口。