今天给自己的博客做了一次升级,本以为只是一次常规的依赖更新,没想到最后却在一个看似简单的“样式错乱”问题上折腾了很久。
一开始,我把 Valaxy 框架升级到了最新版本,希望和作者使用的版本保持一致。构建过程没有报错,本地预览也完全正常,但部署到云服务器后,文章页面的布局却明显不对。
更奇怪的是,本地和线上使用的明明是同一套代码。后来我甚至直接把本地构建好的产物上传到服务器,问题依旧存在。
我先怀疑 Valaxy 的新版本存在兼容问题,又参考了云游君使用同款主题的博客。对方的网站显示正常,于是我把 Valaxy 一路降级到了 0.28.11,结果线上页面还是老样子。
最后没想到,这个困扰了我很久的问题,竟然被 AI 帮我定位并解决了。
问题现象
本地使用下面的命令预览构建产物:
pnpm serve文章页面显示正常:

但同一篇文章部署到云服务器后,整个页面向右偏移,右侧导航栏已经被压瘪了:

因为本地正常、线上异常,最容易产生的第一反应就是:
- 本地和 GitHub Actions 使用的 Node.js 或 pnpm 版本不同;
- Valaxy 新版本存在构建差异;
- 服务器没有上传完整的 CSS;
- 浏览器或 CDN 缓存了旧资源;
- Nginx 返回了错误的 MIME 类型;
- 线上加载了旧版本的构建产物。
这些方向都很合理,但这次真正的问题并不在 CSS。
最初怀疑是 Valaxy 版本问题
这次升级首先把 Valaxy 更新到了最新版本。发现线上异常后,我自然把版本升级列为首要嫌疑。
我对比了云游君使用同款主题的博客,发现对方的网站并没有出现相同问题。为了排除最新版带来的影响,我又把 Valaxy 降级到了 0.28.11。
然而重新安装依赖、重新构建并发布之后,问题依旧存在。
这一步其实已经说明:问题大概率不由某一个 Valaxy 版本直接导致。但当时页面表现得实在太像样式问题,我仍然在构建产物和依赖版本上反复排查。
确认本地与线上静态资源一致
排查的转折点,是不再只凭“看起来像 CSS 错了”判断问题,而是直接比较浏览器实际加载的资源和页面结构。
关于本地和生产环境各种明细比较可以交给 Codex、Claude Code 这类 Agent 来做,不是单纯的 AI 聊天对话工具。
首先检查文章页面引用的主要资源:
assets/style.Bw4NHIdy.css
assets/theme.D1YS81AU.js
assets/index.CoA0Qxzn.js然后分别计算本地文件与服务器响应内容的 SHA-256。结果显示,CSS、主题脚本和入口脚本的文件长度与哈希全部一致。
这意味着:
线上加载的 CSS 和 JavaScript 与本地构建产物完全相同,样式文件本身没有损坏,也没有少上传。
其实移动端适配页面也有问题,后面接下来就比较移动端中的计算样式,文章正文的字体、字号、行高、宽度和内边距也都相同。真正不同的是文章主体的位置:
本地 .yun-main 的顶部位置:约 48px
线上 .yun-main 的顶部位置:约 524px线上页面的正文被额外向下推了约 476px。
进一步检查 DOM 后发现,线上页面顶部残留了一块移动端作者信息卡片,而本地页面没有这块结构。至此可以确定:这不是 CSS 把元素画错了,而是服务器最初返回的 HTML 就不对。
真正原因:Nginx 返回了错误的 HTML
博客文章的访问地址是:
/posts/sql-spatial-query-optimization-tutorial这是一个不带 .html 后缀的 clean URL。
但 Valaxy 静态生成的真实文件是:
/posts/sql-spatial-query-optimization-tutorial.html服务器原来的 Nginx 配置为:
location / {
try_files $uri /index.html;
}这段配置的查找过程是:
- 查找
$uri对应的文件; - 如果找不到,就返回根目录的
/index.html。
当浏览器请求下面的地址时:
/posts/sql-spatial-query-optimization-tutorialNginx 只会查找:
/posts/sql-spatial-query-optimization-tutorial它不会自动继续尝试:
/posts/sql-spatial-query-optimization-tutorial.html由于无扩展名的真实文件不存在,请求最终回退到了博客首页的 /index.html。随后,浏览器中的 Vue Router 又根据当前 URL 加载并渲染了文章内容。结果就变成了:
首页的初始 SSR 结构
+
客户端路由渲染的文章结构
=
顶部残留首页组件,文章布局错位所以页面看起来像“样式错乱”,实际却是 Nginx 给文章 URL 返回了错误的 HTML 文件。
为什么 pnpm serve 没有问题
本地的 Valaxy/Vite 预览服务器支持 clean URL。访问:
/posts/sql-spatial-query-optimization-tutorial预览服务器会自动找到:
/posts/sql-spatial-query-optimization-tutorial.html但 Nginx 的 try_files 不会自动补全 .html,必须在配置中明确写出来。
这也解释了为什么同一份构建产物在本地完全正常,放到服务器上却出现问题:差异不在构建文件,而在服务器如何把 URL 映射到文件。
解决办法
把 Nginx 配置修改为:
location / {
try_files $uri $uri.html $uri/ /index.html;
}新的查找顺序为:
- 查找
$uri对应的真实文件; - 查找
$uri.html; - 查找
$uri/对应的目录; - 全部找不到时,最后才回退到
/index.html。
这段配置分别是什么意思
location / {
try_files $uri $uri.html $uri/ /index.html;
}location / 用于匹配没有被其他更具体规则捕获的普通网站请求。try_files 则按照从左到右的顺序检查文件或目录,找到第一个存在的目标后便停止查找。
$uri:当前请求路径,不包含域名和查询参数。它优先匹配真实存在的 CSS、JavaScript、图片、字体或带后缀页面。$uri.html:在请求路径后补上.html再查找。例如请求/posts/article时,会尝试读取/posts/article.html。这是 Valaxy 无扩展名文章地址能够命中静态 HTML 的关键。$uri/:检查对应目录是否存在,并可配合index指令读取目录下的index.html,例如/about/index.html。/index.html:前面都没有命中时的最终回退入口,通常用于 Vue Router 的 history 模式,由客户端路由继续处理请求。
推荐的顺序可以概括为:
真实文件 → 补全 .html → 真实目录 → SPA 入口顺序很重要。真实静态页面应当优先于 SPA 回退,否则 Nginx 会过早返回首页;如果省略 $uri.html,Valaxy 生成的无扩展名文章 URL 就无法命中对应的 .html 文件。 修改配置后检查语法并重新加载 Nginx:
nginx -t
nginx -s reload修复后,服务器能够为无扩展名文章 URL 返回正确的文章 HTML,页面布局也恢复正常:

如何验证是否命中了正确文件
可以分别请求无扩展名地址和显式 .html 地址:
curl -I https://www.bestguo.top/posts/valaxy-nginx-clean-url-style-problem
curl -I https://www.bestguo.top/posts/valaxy-nginx-clean-url-style-problem.html修复前,无扩展名地址返回的是根目录 index.html,本次排查中响应大小只有约 46KB。
显式访问 .html 时,返回的才是真正的文章文件,大小约为 124KB,并且与本地 dist 文件的 SHA-256 完全一致。
加入 $uri.html 后,无扩展名 URL 也会命中这个正确的文章文件。
如果博客完全采用静态生成,不需要让未知路径回退到 SPA,还可以使用更严格的配置:
location / {
try_files $uri $uri.html $uri/ =404;
}这样不存在的页面会直接返回 404,不会因为统一回退到首页而掩盖静态文件映射问题。
排查小结
这次问题最迷惑的地方,是它在视觉上非常像 CSS 或框架版本故障,但根因却发生在更前面一层:服务器返回了错误的 HTML。
以后再遇到“本地正常、线上异常”,可以按照下面的顺序排查:
- 确认请求的 HTML 是否真的是目标页面;
- 对比本地和线上 CSS、JavaScript 的文件长度与哈希;
- 检查静态资源的状态码、
Content-Type和缓存响应头; - 对比关键元素的计算样式和实际位置;
- 检查 Nginx 的
root、alias、try_files与 clean URL 映射; - 最后再考虑框架版本、依赖和构建环境差异。
还有一个很重要的经验:
“同一份构建产物”只能证明文件内容一致,不能证明服务器一定把正确的文件返回给了当前 URL。
这次我先升级 Valaxy,又降级到 0.28.11,反复构建、上传和刷新,始终没有解决问题。最后通过 AI 对比静态资源哈希、DOM 结构和服务器响应,才发现真正的问题只是 Nginx 少尝试了一次 $uri.html。
有时候最难排查的问题,并不是配置有多复杂,而是表面现象把人带到了完全错误的方向。