首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Vue项目打包部署Nginx:解决白屏、刷新404与缓存问题
📅 2026/10/2 4:56:52
✍️ 爱科研究院
👁 阅读 3,247
打包命令敲下去控制台一路绿字dist目录也生成了然后呢我第一次把 vue 项目往服务器上搬的时候就卡在这个然后。本地npm run dev一切正常传到服务器上要么整页白屏要么刷新一下 404要么路由点着没问题、地址栏一刷新就跳回默认首页。后来带过几个新人发现大家踩的坑高度重合问题基本不在业务代码而在打包产物怎么理解和服务器怎么把它喂给浏览器这两件事上。这篇就把 vue 项目打包后部署到服务器的完整链条拆开讲从dist里每个文件是干什么的到 Nginx 每一行配置为什么这么写再到打包后布局异常、刷新 404、缓存不更新这些真实翻车场景的排查链路。适合刚接触前端上线流程的同学也适合已经部署过但总在细节上返工的同学照着走一遍能省掉大量来回试探的时间。1. 打包产物到底长什么样先看懂 dist 再谈部署1.1 npm run build 究竟做了哪些事不管你是 Vue CLI 还是 Vitenpm run build的本质都是把一堆浏览器看不懂的东西.vue单文件组件、TypeScript、SCSS、ES Module 的import翻译成浏览器能直接执行的静态文件。这个过程里发生的关键动作有这么几个理解它们对后面排错很有用。第一个动作是模块合并与摇树。你在main.js里import了几十个组件打包器要顺着依赖图把它们全部串起来同时把没被引用到的代码删掉。这就是为什么有时候你在工具函数文件里写了个export结果没在项目里用过上线后去搜产物源码压根搜不到。第二个动作是拆分。默认情况下框架运行时Vue 本身、第三方库、你自己的业务代码会被分成不同的 chunk。比如 Vue CLI 默认会产出chunk-vendors.[hash].js和app.[hash].jsVite 则倾向于按动态import()的边界来拆。拆分的意义是缓存友好第三方库几个月不变用户第二次访问就不用重新下载了所以你会在文件名里看到那一串哈希值。第三个动作是压缩与降级。JS 会被 Terser 或 esbuild 压成一行并做变量名混淆CSS 会被合并压缩图片小于阈值的一般会转成 base64 内联。产物名字长得像乱码是正常的不要试图去读它。最终目录结构大致是dist/ ├── index.html ├── favicon.ico ── assets/ ├── index-a1b2c3d4.js ├── index-e5f6g7h8.css ├── chunk-vendors-i9j0k1l2.js └── logo-m3n4o5p6.png这里有个新手最容易忽略的点index.html是入口它里面用script src/assets/index-a1b2c3d4.js这种方式引用资源默认是绝对路径从域名根开始算。你把dist目录双击用file://协议打开一定会白屏因为浏览器会去找file:///assets/index-xxx.js那个路径根本不存在。这不是项目坏了是打开方式错了。1.2 上传之前先在本地用静态服务把 dist 跑一遍我见过太多人跳过这一步直接传服务器然后在服务器上反复改配置效率极低。正确的顺序是先在本地确认dist本身没问题再去折腾服务器。跑本地静态服务有两个层级一定要区分开命令是否支持 history 路由刷新适用场景python3 -m http.server 5000 -d dist不支持只想确认资源能不能加载npx serve dist不支持同上带目录列表npx serve -s dist -l 5000支持会 fallback 到 index.html模拟真实 Nginx 行为区别在哪假设你的路由是/user/123用python3 -m http.server访问这个地址服务器会去找dist/user/123这个文件找不到就 404。而加了-s参数之后任何找不到的路径都返回index.html前端路由就能接管。Nginx 那边的try_files干的正是同一件事先在本地把这层行为验证清楚到服务器上你就知道该找谁的问题了。另外建议顺手加一个--host 0.0.0.0或者用局域网 IP 打开一次用手机连同一个网络访问一下。有些布局问题只在真实移动端浏览器上暴露电脑上拖窄窗口是模拟不出来的。1.3 publicPath 与 base 填错是白屏的头号元凶这个参数在 Vue CLI 里叫publicPath在 Vite 里叫base默认值都是/。它决定了产物里资源引用的前缀。只有当你的站点部署在域名根目录时才应该保持/。三种常见情况部署在https://example.com/配置保持默认产物引用/assets/xxx.js正确。部署在https://example.com/admin/必须改成/admin/否则浏览器会去域名根找资源全部 404页面白屏。部署在二级目录但不想写死路径可以填./让打包器产出相对路径。但这里有个坑相对路径和 history 路由是相冲的。因为路由是/admin/user/123浏览器解析./assets/xxx.js时会以当前路径为基准算出来是/admin/user/assets/xxx.js照样 404。所以只要用了 history 模式就别用相对路径老老实实写完整的子目录前缀。配置位置// vue.config.jsVue CLI module.exports { publicPath: /admin/, outputDir: dist, assetsDir: assets }// vite.config.jsVite import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ base: /admin/, build: { outDir: dist, assetsDir: assets }, plugins: [vue()] })改完这两个参数记得重新build一次别拿旧的dist去部署这个低级错误我自己犯过不止一次。2. 服务器侧的准备工作从裸机到能接住前端包2.1 机器怎么选系统装哪个版本纯前端静态站点的资源消耗极低一台 1 核 2G 的入门配置配合 Nginx扛住每天几千到几万 PV 没什么压力因为 Nginx 处理静态文件的性能非常可观真正吃资源的是后端接口和数据库。如果你还打算在这台机器上跑 Node 服务、数据库、构建任务那就直接上 2 核 4G 起步别在内存上省钱编译时被 OOM 杀掉进程是很挫败的体验。系统方面我一般选 Ubuntu 的 LTS 版本或者 Debian 的 stable。原因很实际软件源里的 Nginx、certbot、rsync 版本都比较新遇到问题搜出来的资料也最多。CentOS 系不是不能用但近两年生态变动大新手容易在装源这一步就卡住。买机器的时候有个细节值得注意带宽比 CPU 更重要。静态站点的 js 包动辄几百 KB1M 带宽的理论下载速度只有 128KB/s 左右用户首屏等待会很难受。预算有限的情况下宁可降一档 CPU也要保证 3M 以上的带宽同时把 gzip 打开能省掉六成以上的传输体积。2.2 前端静态站点到底需不需要装 Node这是被问得最多的问题之一。答案很明确如果你的项目是纯前端 SPA服务器上完全不需要装 Node也不需要 pm2 之类的东西。道理很简单。npm run build是构建时行为不是运行时行为。构建完成之后产物就是一堆 html、js、css、图片浏览器请求它们Nginx 从磁盘读出来返回中间没有任何 JavaScript 执行环境参与。数据从哪来从后端接口来浏览器直接发请求。那什么时候需要 Node 常驻两种情况。一是用了 SSR 框架比如 Nuxt 的服务端渲染模式页面 HTML 需要在服务器上实时生成。二是你的项目里塞了一个 BFF 层也就是浏览器到后端之间的中间接口层专门做接口聚合和数据裁剪。这两种情况才需要node server.js常驻并且用 pm2 做进程守护和开机自启。判断方法很简单看你的package.json里有没有start脚本用来启服务。如果没有就是纯静态Nginx 搞定一切。2.3 目录规划与上传方式的选择服务器上的目录别乱放。我习惯这样规划/var/www/myapp/ ├── releases/ │ ├── 20240512103000/ │ ├── 20240513141500/ │ └── 20240514162000/ └── current - releases/20240514162000releases存放每一次发布的完整产物用时间戳命名方便追溯。current是一个软链接永远指向当前生效的那一份。Nginx 的root指向current发布新版本就是传新目录 改软链接指向回滚就是把软链接指回上一个目录秒级完成不用重新传文件。这套做法借鉴了 Capistrano 的思路用过一次就回不去了。上传方式我列个对比按场景选方式优点缺点适合谁scp -r dist/* userhost:/path简单直接命令短每次全量传慢偶尔发一次的小项目rsync -avz --delete dist/ userhost:/path增量同步只传变化的文件参数需要理解高频发布推荐Git 拉取 服务器上构建一条命令版本可追溯服务器要装 Node构建吃内存团队协作代码托管在私有仓库CI 自动发布完全无人值守规范首次配置有学习成本已有持续集成流程的团队rsync那个--delete参数要特别小心它的作用是删掉目标目录里源目录没有的文件保证两边完全一致。如果路径写错一个斜杠可能把整个目录清空。建议第一次加-ndry run先看看它准备干什么。还有一点rsync源路径结尾的斜杠含义完全不同dist/表示把 dist 里面的内容同步过去dist表示把 dist 这个目录本身同步过去。这个细节坑过无数人。2.4 别用 root 上传权限最小化处理生产环境直接用 root 传文件、跑服务是个坏习惯。正确做法是建一个专门的部署账号比如deploy只给它/var/www/myapp目录的写权限sudo adduser deploy sudo mkdir -p /var/www/myapp/releases sudo chown -R deploy:deploy /var/www/myapp sudo chmod -R 755 /var/www/myappNginx 的工作进程通常会以www-data用户运行它需要能读取这些文件所以目录给 755、文件给 644 就行千万不要图省事给 777那等于把整个目录敞开。配置完成后用ls -l检查一下重点看 Nginx 用户能不能读。有个很隐蔽的场景你从本地传上去的文件带着本地的权限位如果某个文件权限是 600 且属主不是 Nginx 用户Nginx 读不了返回 403而浏览器控制台只显示加载失败很难往权限方向想。3. Nginx 配置的每一行在干什么3.1 一份最小可用的站点配置先看骨架再逐行拆。server { listen 80; server_name example.com www.example.com; root /var/www/myapp/current; index index.html; location / { try_files $uri $uri/ /index.html; } }listen 80是监听端口server_name是域名匹配规则这两个不用多说。root指定网站根目录我这里指向了软链接current所以每次发布换链接就生效了不用改配置。index index.html告诉 Nginx当请求的是目录时默认返回哪个文件。关键是location /这一段它是整个前端部署里最重要的一行。3.2 try_files 与刷新 404history 模式的根因Vue Router 有两种模式。hash模式下地址栏长这样example.com/#/user/123井号后面的内容浏览器不会发给服务器所以服务器永远只看到/不会 404。history模式下去掉了井号地址变成example.com/user/123用户在页面上点链接没问题因为那是前端 JS 拦截的跳转没有真实请求但用户按 F5 刷新浏览器会真真切切地向服务器请求/user/123这个路径而服务器磁盘上根本没有这个文件于是 404。try_files $uri $uri/ /index.html;就是解决这个问题的。它的执行逻辑是先看请求的路径能不能对应到一个真实文件$uri能就直接返回比如/assets/index-a1b2.js会命中真实文件。不能的话看是不是一个目录$uri/比如请求/assets/会尝试找assets/index.html。都不行就返回/index.html交给前端路由去解析。所以用户刷新/user/123时服务器返回的是index.htmlVue 启动后读地址栏发现是/user/123渲染出对应用户页。看起来一切正常。这里埋着一个非常经典的坑后面第 4 章会展开如果某个 js 文件不存在了比如你清理旧版本文件时误删try_files也会返回index.html也就是一段 HTML 内容但响应头里的 Content-Type 还是text/html。浏览器满心期待地要执行这个 js结果拿到一段 HTML控制台会报类似Uncaught SyntaxError: Unexpected token 的错误。这个报错信息看起来特别莫名其妙但只要你知道了机制一眼就能定位。3.3 缓存策略index.html 必须不缓存带哈希的资源可以长期缓存缓存是发布流程里最容易出我明明改了用户看到的还是旧版本问题的地方。核心原则只有一条文件名带哈希的资源可以设置超长缓存不带哈希的入口文件必须禁用强缓存。因为index-a1b2c3d4.js这个文件名本身就是内容指纹内容一变哈希就变文件名就变浏览器自然去请求新文件旧文件缓存在那里也无所谓。而index.html的文件名永远不变如果它被缓存了用户浏览器里存的还是旧的 HTML里面引用的还是旧哈希的 js新版本就永远上不了线。配置写法# 带哈希的静态资源长期缓存 location ~* \.(js|css|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control public, immutable; } # 入口文件不缓存 location /index.html { add_header Cache-Control no-cache, no-store, must-revalidate; add_header Pragma no-cache; expires 0; }这里涉及 Nginx location 的匹配优先级必须搞清楚否则配置写了不生效你还不知道为什么修饰符含义优先级精确匹配路径必须完全一致最高^~前缀匹配命中后不再尝试正则次高~正则匹配区分大小写中~*正则匹配不区分大小写中无修饰符普通前缀匹配最低匹配顺序是先找精确匹配命中就结束否则找最长的前缀匹配如果那个前缀带了^~就用它否则记住这个前缀匹配结果再去按顺序试正则正则一旦命中就用正则的正则都没命中才回退到刚才记住的前缀匹配。所以上面那份配置等价于请求/index.html时location /index.html精确命中不缓存请求别的.js、.css正则命中长缓存请求/user/123这种路径两个规则都不匹配落到location /的try_files。3.4 gzip 该开多大会不会拖慢服务器gzip 是性价比极高的优化。一个 500KB 的 js 文件压缩后通常只剩 130KB 左右用户下载时间直接砍掉一大半。配置gzip on; gzip_comp_level 5; gzip_min_length 1024; gzip_vary on; gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml image/svgxml;几个参数的取舍值得说说。gzip_comp_level范围是 1 到 9级别越高压缩率越好但越吃 CPU。实测下来 5 到 6 是最舒服的区间再往上压缩率提升很有限CPU 开销却成倍增加。静态文件其实更适合在构建阶段预压缩好也就是生成.js.gz文件Nginx 用gzip_static on;直接返回现成的压缩包服务器运行时完全不消耗 CPU 做压缩这是高流量站点的标准做法。gzip_min_length设成 1024 是为了避免压缩小文件因为压缩后的头部开销可能比省下来的还多得不偿失。gzip_vary on会加上Vary: Accept-Encoding响应头告诉中间的任何缓存层同一个 URL 针对不同的编码方式要分别缓存不加的话可能出现有的用户拿到压缩版、有的拿到未压缩版然后解码失败的怪问题。验证是否生效curl -I -H Accept-Encoding: gzip https://example.com/assets/index-a1b2c3d4.js响应头里出现Content-Encoding: gzip就成了。如果没出现检查两件事文件类型是否在gzip_types列表里Nginx 是否重载了配置nginx -s reload或systemctl reload nginx。3.5 把 /api 请求转发给后端避开跨域前端部署到example.com接口在api.example.com:8080浏览器会因为同源策略拦下请求。解决办法是在 Nginx 层做一次请求转发让浏览器以为接口和页面同源。location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 30s; proxy_read_timeout 60s; }这里有一个必踩的坑proxy_pass结尾带不带斜杠转发行为完全不同。proxy_pass http://127.0.0.1:8080/;带斜杠请求/api/user/list会被转发到http://127.0.0.1:8080/user/list/api前缀被剥掉了。proxy_pass http://127.0.0.1:8080;不带斜杠转发到http://127.0.0.1:8080/api/user/list前缀保留。后端接口如果本身就是/api/user/list你却配了带斜杠的版本那就会 404然后你会怀疑人生地去看后端日志。记住这个规则location 和 proxy_pass 至少有一个带斜杠结尾才不会出路径拼接事故。那几个proxy_set_header也是必须的。不加Host后端拿到的域名是127.0.0.1:8080如果后端做了域名校验会直接拒绝。不加X-Real-IP和X-Forwarded-For后端日志里所有请求的来源 IP 都是127.0.0.1出问题没法追踪真实用户。不加X-Forwarded-Proto后端不知道自己是被 HTTP 还是 HTTPS 访问的生成回调地址时会出错。前端代码里就把请求基地址写成相对路径/api不要写死完整域名// 推荐 const baseURL /api // 不推荐写死了域名换环境就得改代码重新打包 const baseURL https://api.example.com4. 打包后布局异常、白屏、样式错乱一次完整的排查链路这一章讲讲真实翻车现场。重点不是给你答案而是给你排查顺序因为同样一个白屏现象背后可能是四五种完全不同的原因按顺序排下去能最快收敛。4.1 白屏的第一现场Console 和 Network 面板打开浏览器开发者工具先看 Console 有没有红色报错再看 Network 里所有请求的状态码。index.html就是 404Nginx 的root路径写错了或者文件根本没传上去。先用ls -l /var/www/myapp/current/index.html在服务器上确认文件存在。HTML 能加载但 js 全部 404典型的publicPath配置错误。去 Network 面板看请求的 URL 是什么如果是https://example.com/assets/xxx.js而你实际部署在/admin/下说明publicPath没改。js 请求返回 200但控制台报Unexpected token 这个前面提过是try_files把不存在的 js 请求 fallback 到了index.html浏览器拿到一段 HTML 当 js 执行。根本原因通常是旧版本文件被删了但用户浏览器还缓存着旧 HTML或者发布时文件没传全。检查方法是直接在浏览器地址栏访问那个 js 的 URL看返回的是代码还是 HTML。js 请求返回 403文件权限问题Nginx 工作用户读不了参考 2.4 节的权限设置。js 是 200 但 MIME 类型不对响应头里Content-Type不是application/javascript。这通常也是 fallback 到 index.html 导致的因为 index.html 的类型是text/html。顺着这条链路走一遍九成白屏问题能定位到根因。4.2 布局异常往往不是代码问题而是资源没加载全打包后布局异常是个高频搜索词我遇到的案例里真正因为代码逻辑出问题的比例很低大多数是下面这几种第一种CSS 文件 404页面变成裸 HTML。表现形式是所有样式全丢文字左对齐、没有背景色、按钮变成原生样式。去 Network 面板过滤.css一看便知。原因和 js 404 一样publicPath或者文件同步不全。第二种字体图标没加载图标位置空出一块或者显示成方块。iconfont、Font Awesome 这类方案依赖字体文件而 CSS 里引用字体文件的路径是相对 CSS 文件本身的位置计算的。如果你的 CSS 和字体文件都被放到assets/下路径正常但如果构建配置把 CSS 输出到别处字体文件路径就会错位。检查方法是在 Network 面板里搜woff、ttf之类的请求。第三种样式覆盖顺序变了。开发环境下各个.vue文件里的style是按组件引入顺序注入的生产环境下会被提取合并成一个 CSS 文件并压缩顺序可能发生变化。如果你写了依赖后来者覆盖的样式比如在某个页面里覆盖公共组件的样式生产环境就可能失效。规范做法是不要在业务页面里去覆盖公共组件样式而是通过 props 或者 CSS 变量来控制。第四种浏览器兼容导致的 flex 或 grid 表现不一致。这个不常见但确实有尤其是用了较新的 CSS 特性又没有配置兼容性处理时。构建时注意browserslist配置要和实际用户群体匹配不要无脑排除旧浏览器。第五种缓存导致的新旧混合。用户浏览器里缓存了旧版本的 CSS同时 HTML 已经更新到新版本js 是新版、css 是旧版两个版本的结构对不上页面就错位了。这就是为什么 3.3 节强调index.html必须不缓存。4.3 我明明改了但页面没变的三层缓存排查这个现象通常是三层缓存在作祟按照从近到远的顺序排第一层浏览器缓存。最直接的验证方式是开无痕窗口或者按 F12 打开 Network 面板勾上 Disable cache 再刷新。如果无痕下正常、正常窗口异常就是浏览器缓存。彻底验证可以硬刷新CtrlShiftR。第二层Nginx 缓存。主要是expires和Cache-Control头设置不当。用curl -I https://example.com/index.html看响应头里的缓存指令确认index.html返回的是no-cache而不是max-age31536000。第三层CDN 或中间缓存层。如果你在 Nginx 前面还挂了 CDN那 CDN 节点上可能有旧内容。这种时候需要在发布流程里加一步刷新 CDN 缓存或者给 HTML 请求配置不缓存规则。这是最容易漏掉的一层很多人排查到最后才发现是 CDN 的问题。排查顺序建议是先无痕验证再 curl 看响应头最后查 CDN。不要一上来就去改 Nginx 配置先确认问题出在哪一层。4.4 移动端上表现不同viewport 与适配方案有些布局问题只在手机上看得到电脑上拖窄浏览器窗口完全正常。常见原因index.html里缺少 viewport meta 标签导致移动端按桌面宽度渲染然后整体缩放看起来字很小、布局挤在一起。正确写法是meta nameviewport contentwidthdevice-width, initial-scale1.0。使用了rem或vw适配方案但基准值没配好或者做了动态计算。这类方案依赖 JS 在页面加载时计算根字号如果 JS 执行晚于首屏渲染会有一瞬间的错位叫做闪动。缓解办法是把关键的首屏样式抽出来内联到index.html里让首屏不依赖外部 CSS。还有一种是软键盘弹起导致的高度变化。手机上输入框获得焦点时可视区域高度会变小如果布局用了100vh内容会被挤压变形。移动端更推荐用100dvh或者干脆用 flex 布局让内容自适应。5. 把它做成一次可复现的发布流程5.1 用一个脚本把打包 上传 切换串起来手工敲命令发几次之后你就会想写脚本。因为手工操作一定会漏步骤比如忘了重新 build 就上传、忘了改软链接、忘了 reload Nginx。服务器端的部署脚本思路是这样#!/bin/bash set -e APP_DIR/var/www/myapp RELEASE_DIR$APP_DIR/releases/$(date %Y%m%d%H%M%S) mkdir -p $RELEASE_DIR # 从标准输入解压上传上来的产物 tar -xzf - -C $RELEASE_DIR # 切换软链接用 -n 避免在已有软链接时创建嵌套目录 ln -sfn $RELEASE_DIR $APP_DIR/current # 保留最近 5 个版本其余删掉 cd $APP_DIR/releases ls -1dt */ | tail -n 6 | xargs -r rm -rf # 检查 Nginx 配置语法后重载 nginx -t nginx -s reload echo deployed: $RELEASE_DIR本地这边配合#!/bin/bash set -e npm run build tar -czf - -C dist . | ssh deployexample.com bash /var/www/myapp/deploy.sh用 tar 通过标准输入流管道传过去省掉了先在服务器落一个临时文件再解压的步骤也能保持目录权限。set -e的作用是任何一步出错就立刻终止避免带着错误继续往下跑。ln -sfn里的-n参数要特别注意。如果目标是已存在的软链接不加-n的话ln会在软链接指向的目录里面创建新链接结果就是current/current/xxx这种诡异的嵌套结构网站直接挂掉。5.2 版本化目录带来的回滚能力用了releases加current软链接的结构之后回滚变得异常简单cd /var/www/myapp/releases ls -1dt */ ln -sfn /var/www/myapp/releases/20240513141500 /var/www/myapp/current把软链接指回上一个版本Nginx 不用重启新请求立刻就走旧版本了。整个过程一两秒不需要重新上传文件。有这套机制之后遇到线上问题可以先回滚止血再慢慢排查原因心态会从容很多。需要提醒的是回滚之后记得把出问题的版本目录留着别删方便后续分析。清理策略里保留 5 个版本就是出于这个考虑既能省磁盘又给排查留了余量。5.3 用持续集成把发布变成自动动作团队协作场景下手工发布早晚会出事。接入 CI 之后流程变成合并到主分支自动构建并发布。以常见的 CI 平台为例核心配置大概是这个形态deploy: stage: deploy only: - main script: - npm ci - npm run build - tar -czf - -C dist . | ssh -o StrictHostKeyCheckingno deployexample.com bash /var/www/myapp/deploy.sh environment: name: production url: https://example.com有三件事必须提前准备好否则 CI 会卡住。第一是免密登录。CI 机器上要有一对密钥公钥放到服务器的deploy用户下私钥配到 CI 的密钥变量里注意私钥要用平台的加密变量存储绝对不能写在代码仓库里。第二是npm ci而不是npm install。npm ci严格按照package-lock.json安装保证每次构建的依赖版本完全一致避免我本地能跑线上不行这种问题。第三是环境变量。接口地址这类配置项通过环境变量注入在.env.production或者 CI 的变量配置里定义构建时被打包进去。不要把敏感信息硬编码在源码里。还有一个经验CI 里的构建失败一定要有通知。不然某次发布失败了但没人注意到线上还是旧版本问题会以更奇怪的方式暴露出来。6. HTTPS、域名与上线后的收尾检查6.1 证书配置与 HTTP 自动跳转现在浏览器对 HTTP 站点的态度越来越严格很多 API 在非安全上下文下直接不可用比如地理位置、摄像头、剪贴板。所以 HTTPS 基本是必须的。申请免费证书的流程很成熟装好工具之后一条命令就能把证书签下来并自动改写 Nginx 配置。证书签完之后Nginx 配置会变成两个 server 块server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name example.com www.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; root /var/www/myapp/current; index index.html; location / { try_files $uri $uri/ /index.html; } }listen 80那个块只做一件事把所有 HTTP 请求 301 重定向到 HTTPS。这里要注意$host$request_uri的写法$request_uri保留完整的路径和查询参数用$uri的话查询参数会丢用户从带参数链接进来会跳到首页。免费证书通常 90 天有效期一定要配置自动续期任务或者至少设置到期提醒。我见过不少站点因为忘了续期某天早上突然全站报证书过期用户体验极差。配置好自动续期之后可以手动跑一次演练命令验证流程能走通。6.2 上线后必须过一遍的检查清单发布完之后别急着关终端花三分钟过一遍下面这些curl -I https://example.com/看状态码是不是 200Content-Type是不是text/html。curl -I https://example.com/assets/index-xxx.js看是不是 200 且类型正确缓存头是不是长缓存。curl -I -H Accept-Encoding: gzip https://example.com/assets/index-xxx.js确认Content-Encoding: gzip存在。浏览器无痕窗口打开走一遍核心业务流程特别是刷新页面和直接访问深层路由。手机上打开一次确认布局没崩。检查favicon.ico虽然是小东西但 404 会在控制台留红字强迫症看着难受。确认 404 页面配置正确用户输错地址时不会看到 Nginx 的默认错误页。这几项花不了几分钟但能拦下绝大多数上线后才发现的问题。最后分享一个我自己的教训。有一次上线后收到用户反馈说页面空白我第一反应是代码有问题回滚之后发现旧版本也空白了这才意识到不是新版本引入的。查了快两小时最后发现是同一次发布时我用rsync --delete同步源目录路径多写了一层导致assets目录被整个删掉了而那次回滚只切了软链接没恢复文件。从那以后我改了两件事一是发布脚本里加了产物完整性检查确认index.html和assets目录都存在且非空才切换软链接二十每次发布后第一件事就是无痕窗口打开线上地址确认页面能出来。这些看起来啰嗦的检查恰恰是把偶尔出事故变成一直不出事故的关键。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/2 4:56:52
上海聚氨酯包胶轮生产厂家哪家好 科冶橡塑综合实力推荐
2026/10/2 4:56:52
从Prompt到Harness:AI工作流工程框架入门与生产落地指南
2026/10/2 4:56:52
多实例隔离实战:单机并行运行多个 LLM 推理服务
2026/10/2 5:41:54
Redis如何成为AI应用的实时协同中枢
2026/10/2 5:41:54
AI Skill四步法:重建个人生产力操作系统
2026/10/2 5:41:54
智能体技能(Agent Skills):从文档到可执行能力单元的工程实践
2026/10/2 5:41:54
Redis作为AI Agent状态中枢:MCP协议实践指南
2026/10/2 5:41:54
AI工程化实战:从数据处理到模型部署的端到端指南
2026/10/2 5:36:54
灯塔工厂四层解耦架构:OT/IT时序一致性设计
2026/10/2 0:01:33
Jev模型详解:从本地部署到Codex接入与数据系统构建
2026/10/2 0:01:33
Paperclip:轻量级AI Agent编排中间件实战指南
2026/10/2 0:01:33
DeepSpeed ZeRO-3 与 MoE 训练实战:显存优化与通信调优
2026/10/1 22:21:25
网站建设的英语怎么说?别只背单词,看完这套安全完整流程才敢上线
2026/10/1 8:09:25
新手入门看这篇:建设网站加盟避坑指南与SEO实操
2026/10/1 21:38:34
论文AIGC疑似度是什么意思?想查论文AI率有哪些免费工具?
2026/10/1 0:01:36
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/2 4:07:50
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/1 0:01:36
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)