我们把网站从HTML切到Nuxt 3,一天踩了5个坑(附检查清单)

SFD实验室真实案例:从HTML静态站迁移到Nuxt 3 SSR,一天踩了5个坑。Nginx图片404、Mock数据未清理、SSR源码异常、UI对比度问题、验收流程缺失——附完整架构迁移检查清单。

标签:Nuxt3SSR架构迁移避坑Nginx前端工程化
专属插画
我们把网站从HTML切到Nuxt 3,一天踩了5个坑(附检查清单)

我们把网站从HTML切到Nuxt 3,一天踩了5个坑(附检查清单)

前言:一天搞定架构迁移,代价是什么?

3月21日,SFD实验室把官网从纯HTML静态站迁移到了Nuxt 3 SSR。

听起来很酷对吧?一天搞定架构升级,现代化框架、服务端渲染、SEO友好——简历上又多一行。

但实际情况是:老板打开首页,插画全部破图。点进文章,"文章不存在"。手机端汉堡菜单看不见。curl一下源码,满眼not-found。

一天之内,我们踩了5个坑,每个都是架构切换时最容易忽略的盲区。今天把这些坑全部摊开聊,省得你再走一遍。

坑1:Nginx切了proxy_pass,图片全炸了

💥 现象

切换上线后老板第一个打开首页,所有插画——全部破图。控制台一排排404,/uploads/和/images/下面的图片一个都加载不出来。

🔍 排查

旧站是纯HTML,Nginx直接serve那个目录,/uploads/logo.png这种路径天然就能访问到文件。但切了proxy_pass给Nuxt的Node进程之后,请求全部转发到了3000端口。Node进程里压根没有这些静态文件——它只管渲染Vue组件,哪知道你的图片在磁盘哪个角落?

🎯 根因

架构切换时只改了主路由的转发,完全忘了静态资源还在旧目录里。proxy_pass是个"全拦截"的操作,你没单独放行的路径,全都会被转发到Node。

🔧 修复

在Nginx配置里加了3个location block:

location /uploads/ {
    alias /var/www/old-site/uploads/;
}

location /images/ {
alias /var/www/old-site/images/;
}

location /api/ {
proxy_pass http://127.0.0.1:8370;
}

就这么几行,但不写就全炸。

📝 教训

切proxy_pass之前,先把旧站所有的静态资源路径列出来。用grep或者find扫一遍HTML/CSS/JS里引用的路径,确保每一条在新架构下都有人"接住"。不要假设"Nuxt会自动处理"——它不会。

坑2:文章详情页全部"文章不存在"

💥 现象

首页看着挺好,文章列表也有数据。但只要点进任何一篇文章的详情页——"文章不存在"。所有文章,无一例外。

🔍 排查

翻前端代码一看,好家伙。变色龙同学写了一堆Mock假数据:mockSciencePosts、mockTechArticles……列表页用的是这些假数据,所以首页和列表看起来正常。但详情页需要根据slug去查具体文章,Mock数据里没有对应内容,直接返回空。

验收的时候呢?只看了首页和列表页。没人点进文章详情。

🎯 根因

两个问题叠加:一是开发用Mock数据做原型没问题,但上线前没替换成真实API调用;二是验收不够深,只看了"第一层"。

修的时候还踩了个额外的坑:lang参数不匹配。前端传的是"zh-CN",但CMS API只认"zh"。就这两个字符的差异,查了好一会儿。

🔧 修复

删掉了300多行Mock数据代码,全部替换成useAsyncData + $fetch直接调CMS的REST API。lang参数做了映射,"zh-CN"→"zh","en-US"→"en"。

📝 教训

上线前必须grep全项目"mock"关键词。这不是建议,是铁律。Mock数据就像脚手架,盖楼的时候需要,交房的时候必须拆干净。不然住进去的人(用户)看到的就是毛坯。

验收也一样:不能只看首页。必须点开至少3篇文章,看详情页能不能正常渲染。

坑3:SSR源码里藏着"not-found"

💥 现象

这个坑比较隐蔽。用浏览器打开页面,一切正常。但用curl看页面源码——

$ curl -s https://v2.smallfiredragon.com/science/xxx | grep not-found
<div class="not-found">...

源码里有not-found标记。搜索引擎爬虫看到的就是这个源码,这意味着SEO直接废了。

🔍 排查

Nuxt 3的SSR是这么工作的:Node服务端先渲染一遍HTML发给浏览器,然后客户端拿到HTML后再"hydrate"(激活),客户端会重新fetch数据。

问题出在服务端渲染这一步。Node进程在服务器内部fetch CMS API时,走的域名是v2.smallfiredragon.com——但服务器自己解析不到这个域名(DNS没配内网解析)。所以服务端fetch失败,渲染出了not-found状态。

浏览器为什么正常?因为客户端hydrate后重新fetch,走的是公网DNS,能解析到,所以又获取到了数据,覆盖了服务端的错误状态。用户看不出来,但爬虫看到的是服务端那份错误的HTML。

🎯 根因

SSR环境下的网络请求和客户端完全不同。服务端在内网,DNS解析、防火墙规则、hosts文件都可能不一样。这个差异在开发环境里根本测不出来,因为开发机上什么都能解析。

🔧 修复

两种方案:在服务器的/etc/hosts里加一条域名解析记录,或者在Nuxt的runtimeConfig里把SSR端的API地址改成内网回环地址(127.0.0.1:8370)。我们两个都做了,双保险。

📝 教训

部署SSR应用,一定要用curl检查服务端渲染出来的HTML。浏览器看到的≠搜索引擎看到的。curl才是爬虫的视角。每次部署后跑一遍curl检查,是SSR项目的必修课。

坑4:汉堡菜单"隐身"了

💥 现象

手机端打开网站,右上角应该有个汉堡菜单图标——但什么都看不见。不是没渲染,是渲染了但看不见。

🔍 排查

打开DevTools一看,元素在那里,有宽高,位置也对。打开Computed看颜色:#94A3B8,一个偏冷的中灰色。背景色?#111318,深蓝黑色。

数字上对比度勉强过线,但实际在手机小屏上看,细线条图标配上这个灰色——真的很难注意到。尤其是在户外强光下或者屏幕亮度偏低的时候,约等于隐身。

🎯 根因

深色主题下的交互元素颜色没有做专项审查。桌面端大屏上可能勉强能看,手机端小屏就直接"消失"了。

🔧 修复

把图标颜色改成品牌橙#F97316。橙色在深色背景上对比度拉满,而且和品牌色统一,一举两得。

📝 教训

深色主题不是把背景改黑就完了。每个交互元素都要重新审视颜色对比度。建议上线前用WebAIM的Contrast Checker跑一遍所有可交互元素,尤其是图标、按钮边框、次级文字这些容易忽略的地方。

坑5:老板的提醒被当耳旁风了

💥 现象

下午17:53,老板在群里发了一句:"注意文章路径是否一样"。

团队的反应:检查了一下HTTP状态码,200,没问题!继续搞别的去了。

然后就出了坑2和坑3。

🔍 排查

回过头看,老板说的"注意文章路径"其实暗含了好几层意思:路径能不能访问?访问了返回什么?内容对不对?SEO路径有没有变?旧链接还能不能跳转?

但团队只理解了最表面的一层:"路径能访问" → HTTP 200 → 没问题。

🎯 根因

HTTP 200只代表"服务器正常响应了",不代表"页面内容是对的"。一个显示"文章不存在"的页面,照样返回200。

🔧 修复

没有代码修复,这是流程问题。后来团队定了一条规矩:老板的提醒 = 风险信号,必须深入排查到内容层。不是检查状态码就完了,而是要打开页面看内容、看源码、测交互。

📝 教训

200 OK是最不可靠的"一切正常"信号。验收必须看实际内容,不是看状态码。这一条不止适用于架构迁移——任何部署后的检查都一样。


SFD编者注

这5个坑,每一个单独拿出来看都不难修。但叠在一起,再加上"一天搞定"的时间压力,就变成了一场事故接力赛。

我们实验室这次迁移最大的收获不是技术层面的——Nginx配置、Mock数据清理、DNS解析,这些搜一下都能查到。最大的收获是对"验收"这件事的重新理解:

你以为验收是打开首页看一眼。实际上验收是模拟用户的完整操作路径,包括那些你觉得"肯定没问题"的页面。

尤其是架构迁移这种事,改的是地基。地基一换,上面的每一层楼、每一扇窗、每一根水管都可能受影响。不能只检查大门能不能开,要每个房间都走一遍。

📋 架构迁移检查清单

最后整理了一份清单,我们内部在用,分享出来:

一、迁移前

  • □ 列出旧站所有静态资源路径(/uploads/、/images/、/fonts/、/assets/等)
  • □ 列出旧站所有API接口地址
  • □ 列出旧站所有文章/页面URL,导出一份完整URL清单
  • □ grep全项目"mock"、"fake"、"dummy"、"test"关键词,标记所有待替换的假数据
  • □ 确认新架构的服务端能否解析所有必需域名(curl测试,不要用浏览器)
  • □ 确认CMS API的参数格式(lang、slug等字段的命名和值)
  • □ 备份旧站配置(Nginx conf、DNS记录、SSL证书路径)

二、Nginx配置切换

  • □ 为每个静态资源路径添加独立的location block
  • □ API转发单独配location(不要混在主proxy_pass里)
  • □ 测试每个location block是否生效(curl每个路径)
  • □ 检查SSL证书是否正确绑定到新配置
  • □ 旧URL做301重定向到新URL(如果路径结构有变化)

三、SSR专项检查

  • □ 用curl检查首页源码,确认有实际内容(不是空壳或not-found)
  • □ 用curl检查至少3个文章详情页源码
  • □ 确认服务端DNS能解析所有API域名(在服务器上nslookup/dig测试)
  • □ 检查SSR环境变量是否正确设置(API地址、语言参数等)
  • □ 对比服务端渲染结果和客户端渲染结果,不应有内容差异

四、内容与UI验收

  • □ 打开首页,检查所有图片是否加载
  • □ 点进至少3篇文章详情页,确认内容正常显示
  • □ 测试所有语言版本(中/英/日等),确认切换正常
  • □ 手机端测试:汉堡菜单、导航、文章页、图片
  • □ 检查所有交互元素在深色/浅色主题下的可见性
  • □ 检查页面title、meta description、OG tags是否正确

五、上线后监控

  • □ 旧URL全量测试(用导出的URL清单跑一遍,检查是否301/200)
  • □ Google Search Console检查索引状态(上线后1-3天)
  • □ 监控服务器错误日志(Nginx error log + Node进程日志)
  • □ 让团队外的人(或老板)随意浏览,发现你漏掉的问题

这份清单不长,但每一条都是我们用真实翻车换来的。建议存下来,下次架构迁移前打开对一遍。

毕竟,踩坑不可怕,可怕的是同一个坑踩两次。