【Nginx 从入门到实战 09】前后端统一入口:部署页面与接口的完整配置

上一篇,我们完成了后端接口转发。这一篇把前面学过的配置组合起来:页面能打开、前端路由能刷新、静态资源不乱回退、接口由后端处理。

继续使用 Windows + CMD。本篇提供一个不依赖框架的最小单页应用,部署行为与常见客户端 Vue、React 项目类似,不需要先创建一套管理后台。

一、先确定四类请求的分工

本篇使用 Nginx 8095 端口作为浏览器统一入口,后端监听本机 3009 端口。

请求 应该怎样处理
/ 返回首页
/users 返回首页文件,由前端渲染用户页
/assets/app.js 返回真实脚本文件,不存在就 404
/api/users 转发到后端 /users 接口

这里 /users 是页面路由,/api/users 是接口路径。两者看起来接近,处理方式不同。

最重要的边界是:

  • 页面路由找不到磁盘文件,可以回退到 index.html。
  • 静态资源找不到,应该返回 404。
  • 接口交给后端,不能错误返回前端首页。

二、准备最小前后端示例

1. 放置文件

下载随文示例包,将文件按下面的位置复制到 Nginx 安装目录。本文以 D:\devtools\nginx 为例,路径不同就替换成自己的。

示例包中的位置 复制到安装目录中的位置
demo/html/lesson09 html/lesson09
demo/lesson09-backend lesson09-backend

复制后确认以下文件存在:

text 复制代码
html/lesson09/index.html
html/lesson09/assets/style.css
html/lesson09/assets/app.js
lesson09-backend/server.cjs

index.html 应直接在 lesson09 中,不要多套一层 demo 或 html。

本篇重点是部署配置,前端完整源码已随包提供。它通过浏览器 History API 切换 / 与 /users,用户页按钮使用:

javascript 复制代码
fetch('/api/users')

前端使用当前站点的接口地址,不写死后端端口。样式与脚本同样使用根路径:

html 复制代码
<link rel="stylesheet" href="/assets/style.css">
<script src="/assets/app.js" defer></script>

2. 启动后端

确认已经有现代 Node.js 环境:

cmd 复制代码
node -v

在 CMD 中执行:

cmd 复制代码
cd /d D:\devtools\nginx
node lesson09-backend\server.cjs

后端不需要 npm install。保持窗口打开,再开一个 CMD 操作 Nginx。

本篇后端完整代码如下,也可以手动创建对应文件:

javascript 复制代码
const http = require('node:http');
const routes = {
    '/health': { status: 'ok' },
    '/users': [{ id: 1, name: 'Maple Hush' }]
};
http.createServer((req, res) => {
    const url = new URL(req.url, 'http://127.0.0.1:3009');
    const found = Object.hasOwn(routes, url.pathname);
    const status = req.method !== 'GET' ? 405 : found ? 200 : 404;
    res.writeHead(status, {
        'Content-Type': 'application/json; charset=utf-8',
        ...(status === 405 ? { Allow: 'GET' } : {})
    });
    res.end(JSON.stringify({
        service: 'lesson09-backend',
        receivedUrl: req.url,
        data: status === 200 ? routes[url.pathname] : null,
        message: status === 200 ? 'success' : status === 404 ? 'route not found' : 'method not allowed'
    }, null, 2));
    console.log(`${req.method} ${req.url} -> ${status}`);
}).listen(3009, '127.0.0.1', () => {
    console.log('Backend ready: http://127.0.0.1:3009');
});

先通过浏览器直连:

text 复制代码
http://127.0.0.1:3009/health

应看到 data.status 为 ok 的 JSON。用户列表地址为:

text 复制代码
http://127.0.0.1:3009/users

后端只提供 GET /health、/users;不存在的接口返回 404 JSON。这个简单服务用于本地实验。

三、组合完整站点配置

1. 备份并打开配置

在第二个 CMD 窗口执行:

cmd 复制代码
cd /d D:\devtools\nginx
copy conf\nginx.conf conf\nginx.conf.lesson09.bak
notepad conf\nginx.conf

如果备份已存在,先确认是否要保留,再决定是否覆盖。

2. 在现有 http 内新增 server

将下面的 server 与其他站点平级放置,不要覆盖整份 nginx.conf:

nginx 复制代码
server {
    listen 127.0.0.1:8095;
    server_name localhost;

    root html/lesson09;
    index index.html;
    charset utf-8;
    access_log logs/lesson09_access.log;
    error_log logs/lesson09_error.log;

    location = /api {
        return 404;
    }

    location ^~ /api/ {
        proxy_pass http://127.0.0.1:3009/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location ^~ /assets/ {
        try_files $uri =404;
    }

    location = /index.html {
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

确认现有 http 内保留 MIME 类型映射,已有时不要重复添加:

nginx 复制代码
include mime.types;
default_type application/octet-stream;

不要复制第六篇诊断实验中的 types { }。本篇需要正确返回 HTML、CSS 和 JavaScript 的类型。

两个服务都只绑定本机回环地址,属于本地学习配置。端口被占用时,修改监听端口和相关访问地址;后端换端口时,proxy_pass 也要一起改。

3. 检查并加载

cmd 复制代码
nginx -t

检查通过后,实例已运行时执行:

cmd 复制代码
nginx -s reload

尚未运行时执行:

cmd 复制代码
start nginx

四、为什么要分开写这些 location?

1. /api/:明确交给后端

nginx 复制代码
location ^~ /api/ {
    proxy_pass http://127.0.0.1:3009/;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

本例会将 /api/users 转发成后端 /users。因为后端没有 /api 前缀,所以使用带 / 的 proxy_pass。2

^~ 使本篇的 /api/ 最长前缀匹配跳过正则检查。当前配置没有正则,行为本来就清晰;保留这个写法,明确该前缀由代理处理。以后新增更长前缀时,仍需重新分析匹配结果。1

接口没找到时,后端返回的 404 会传回浏览器。不要为了展示友好页面,在这个站点里全局把 404 都改成 /index.html。

对于单独的 /api,本例用精确匹配直接返回 404,避免把它当作前端页面。真正的接口仍使用 /api/...。

2. /assets/:只返回真实资源

nginx 复制代码
location ^~ /assets/ {
    try_files $uri =404;
}

资源存在就提供文件,不存在就返回 404。1

例如 /assets/missing.js 不应该返回首页 HTML。否则浏览器可能报脚本类型错误,而你看到的状态码却是 200,容易误判。

这里保护的是 /assets/ 这一个资源目录。实际项目如果还有 /images/、/fonts/ 等目录,需要按真实布局设置同类规则;不能认为所有资源路径都已经被覆盖。

3. /:允许前端页面回退

nginx 复制代码
location / {
    try_files $uri $uri/ /index.html;
}

请求 /users 时,磁盘里没有对应文件或目录,于是内部回退到 /index.html,再由前端脚本根据当前 URL 渲染用户页。1

浏览器地址仍是 /users。这解决的是 History 路由的直接访问和刷新问题。

回退并不会替前端创建页面:如果访问 /unknown-page,Nginx 也可能返回首页文件,但示例脚本会显示"页面不存在"。本例的 HTTP 状态仍是 200,这与后端接口的 404 是不同的概念。

如果确实存在同名目录,$uri/ 的目录检查也会影响后续行为。不要在部署目录里随意混入与页面路由同名的文件夹。

4. /index.html:入口文件也必须真实存在

nginx 复制代码
location = /index.html {
    try_files $uri =404;
}

这是回退的终点。如果入口文件缺失,直接返回 404,避免继续把 /index.html 回退给自己而形成内部重定向循环。

首页回退只适用于对应的前端应用,不等于任何错误都返回首页。

五、按这张清单验证部署

先打开:

text 复制代码
http://127.0.0.1:8095/

应看到浅绿色背景的测试页面。点击"用户页",地址变为 /users,再点击"请求用户接口",应显示 HTTP 200 和用户 JSON。

接下来逐项检查:

操作或请求 预期结果
从首页点击用户页 地址变成 /users,显示用户页
在 /users 按 F5 仍显示用户页,样式与脚本正常
新标签页直接打开 /users 能正常进入用户页
/assets/app.js 返回 JavaScript 内容
/assets/missing.js 404,不返回首页
/api/users 200 JSON,后端收到 /users
/api/missing 后端 404 JSON,不返回首页
/api Nginx 404,本例没有定义该接口
/unknown-page 返回应用入口,前端显示"页面不存在"

浏览器按 F12,打开 Network 面板,再刷新一次。不要只看页面有没有内容,还要确认:

  • HTML、CSS、JS 请求都返回对应内容与类型。
  • /api/users 的响应是 JSON。
  • 缺失资源和接口没有被错误包装成首页。

使用缓存时可能看到 304 或缓存标记,不必把它们当作失败。

六、换成自己的 Vue、React 项目

1. 先构建,再部署产物

在前端项目目录执行项目提供的构建脚本,例如:

cmd 复制代码
npm run build

Vite 项目的默认输出目录通常是 dist,但可以通过配置修改。3

把输出目录中的全部内容 复制到站点目录,让 root 指向真正包含 index.html 的位置。不要只复制 HTML,也不要直接部署 src 源码目录。

本篇手写应用没有构建步骤;换成真实项目时,才执行你自己项目的构建命令。

2. 对齐接口地址

生产构建中的接口地址应按项目约定使用当前站点的 /api 前缀,例如:

javascript 复制代码
fetch('/api/users')

如果开发时配置过 Vite 的 server.proxy,要记住它服务于开发服务器。构建文件交给 Nginx 后,请求转发需要由 Nginx 等部署层完成。

环境变量改了,通常还要重新构建,不能只改本地 .env 就期待已经发布的静态文件自动变化。

3. 对齐后端路径

如果真实后端接口就是 /api/users,需要保留前缀:

nginx 复制代码
proxy_pass http://127.0.0.1:3009;

这与本篇后端 /users 的约定不同。先确认后端路由,再决定斜杠,不要两边重复加或重复去掉前缀。2

4. 对齐部署根路径

本篇部署在站点根路径,资源地址使用 /assets/...。

如果以后部署到 /admin/ 这样的子路径,构建工具的资源基础路径、前端路由基础路径和 Nginx 映射都要同步调整,不能只修改 root。

SSR 应用可能还需要 Node.js 等应用服务,不能直接套用纯客户端静态部署。3

七、常见问题

1. 点击页面能用,刷新 /users 却 404

先检查请求是否进入正确的 8095 站点,再看是否配置了对应的首页回退,以及 index.html 是否真实存在。

前端点击跳转可能只修改浏览器历史记录;刷新则会向服务器重新请求 /users,这是两种不同的过程。

2. 接口返回 200,但前端提示 JSON 解析失败

先查看响应正文。如果是 <!DOCTYPE html> 开头,通常说明接口被页面回退接走了。

检查 /api/ 匹配、前端请求地址,以及是否配置了全局 404 首页回退。

3. 页面正常,接口返回 502

静态文件与后端进程独立。先直连:

text 复制代码
http://127.0.0.1:3009/health

再查看错误日志:

cmd 复制代码
type logs\lesson09_error.log

检查后端是否启动、上游地址和端口是否正确。不要因为首页能打开,就认为后端也一定运行正常。

4. 缺少脚本,却返回了 HTML

核对脚本的真实请求路径:是否确实在 /assets/ 下?构建产物是否完整复制?资源基础路径是否正确?

如果实际资源位于其他目录,要为真实目录设置处理规则,而不是只保留本篇的 /assets/。

5. 修改后没有生效

修改静态文件后,先保存并刷新,必要时禁用缓存检查。

修改 Nginx 配置后,执行 nginx -t,通过后 reload。修改后端代码时,本篇简单 Node.js 服务需要停止后重新运行。

本篇还记录了访问日志:

cmd 复制代码
type logs\lesson09_access.log

根据请求路径、时间和状态码排查,不要只靠反复重启。

八、本篇回顾

现在我们把静态资源服务、location 匹配、路径处理和反向代理组合成了一个可验证的站点。

最重要的是分清边界:

  1. 页面路由由前端处理,必要时回退到入口文件。
  2. 静态资源必须真实存在,缺失时返回 404。
  3. 接口由后端处理,不混入前端页面回退。
  4. 浏览器统一访问 Nginx,后端地址由代理配置管理。

下一篇:《【Nginx 从入门到实战 10】负载均衡入门:把请求分配给多个后端实例》。

我们会启动两个可辨认的后端实例,观察请求分配,并模拟其中一个实例停止后的表现。

参考资料

  1. Nginx 核心模块:location、try_files
  2. Nginx 代理模块:proxy_pass
  3. Vite 静态部署指南
  4. Vite 开发服务器配置:server.proxy
相关推荐
无言听风雨丶4 小时前
Nginx 深度实战:从架构原理到生产排错
运维·nginx·架构
xxwl5856 小时前
Nginx 基础学习笔记
笔记·学习·nginx
孟陬6 小时前
技术周刊#158:ChatGPT 智能 UI 底层原理、个人智能体协议正式发布、一条 POST 请求即可锁死 Next.js 服务
人工智能·后端·全栈
fb_123457 小时前
K8s StatefulSet五大有状态集群实战_Nginx_Etcd_Redis_Cassandra_MySQL
nginx·kubernetes·etcd
京东云开发者7 小时前
AI 会写代码之后,交付为什么还没变快?
全栈
acd120098 小时前
域名解析缓存带来的隐性故障,很多排查都漏掉了这一层
java·运维·nginx
月夏9 小时前
一个汉字存成了 6 个字节:我用 HEX 揪出 MySQL 双重编码
mysql·docker·全栈
枫叶轻语1 天前
【Nginx 从入门到实战 07】root 与 alias:资源路径到底怎样对应?
nginx·全栈
SL_staff1 天前
10人团队如何用轻量私有化文档平台实现知识复用提效?技术视角拆解B/S架构下的协作闭环
java·开源·全栈