错误概述

  • Uncaught SyntaxError: Invalid or unexpected token
    通常由 JavaScript 代码中存在非法字符或语法错误导致,例如未闭合的字符串、特殊符号未转义等。
  • Failed to load module script: MIME type mismatch
    浏览器尝试加载模块脚本(<script type="module">),但服务器返回的响应头 Content-Typetext/html,而非预期的 text/javascriptapplication/wasm

原因分析

语法错误(SyntaxError)

  • 代码中存在拼写错误、非法字符(如中文标点)、JSON 格式错误等。
  • 动态生成的 JavaScript 内容未正确转义。

模块脚本加载失败(MIME type mismatch)

  • 服务器未正确配置静态资源的 MIME 类型。
  • 文件路径错误导致服务器返回了 HTML 错误页面(如 404)。
  • 使用了相对路径或错误的 CDN 地址。

解决方法

检查并修复语法错误

  • 使用开发者工具(Chrome DevTools)查看具体报错位置,定位非法字符或语法问题。
  • 确保动态生成的 JavaScript 内容(如通过 innerHTML 或模板字符串)经过转义处理。

验证模块脚本的 MIME 类型

  • 在浏览器 Network 面板中检查相关 .js 文件的响应头,确认 Content-Typetext/javascript
  • 若服务器返回的是 HTML(如 404 页面),需检查文件路径是否正确。

配置服务器 MIME 类型

  • Nginx: 在配置文件中添加以下规则:
    location ~* \.js$ {
      add_header Content-Type text/javascript;
    }
    
  • Apache: 在 .htaccess 中添加:
    AddType text/javascript .js
    
  • Node.js (Express): 使用 mime 模块或静态中间件:
    express.static('public', { setHeaders: (res) => res.set('Content-Type', 'text/javascript') })
    

修正文件路径

  • 确保模块脚本的引用路径为绝对路径或正确的相对路径。
  • 若使用 CDN,检查资源 URL 是否有效。

其他排查步骤

  • 清除浏览器缓存,避免旧版本脚本残留。
  • 检查构建工具(如 Webpack/Vite)是否生成错误的输出路径。

预防措施

  • 使用 ESLint 或 TypeScript 提前捕获语法错误。
  • 在开发环境中启用严格的 MIME 类型检查。
  • 部署前通过自动化测试验证资源加载。

参考案例

  • 动态加载模块时,确保使用正确的 import() 语法:
    import('./module.js').catch(err => console.error('加载失败:', err));
    
  • 若使用框架(如 React/Vue),检查构建配置是否正确处理模块依赖。

以上部分是AI的说法,接下来我说说我自己的说法,也是我在项目中遇到的解决办法

这个是我在做vue项目的时候,并且用上了**PWA(渐进式 Web 应用)**的时候,部署到线上遇到的问题

旧 SW 从缓存返回旧的 index.html,旧 index.html 引用的旧 JS 文件在服务器上已经被新部署替换掉了,nginx 对找不到的 .js 文件返回 index.html(text/html),浏览器就报这个错。

我们刚才做的 navigateFallbackDenylist: [/./] 改动正好能彻底解决这个问题:

旧行为:SW 从缓存返回旧 index.html → 引用旧 JS → 旧 JS 不存在 → MIME 错误
新行为:SW 不再缓存返回任何 index.html → 每次都从服务器拿最新的 → 引用的 JS 一定在服务器上存在 → 不会报错
但这需要新 SW 激活后才生效。让我再加一层保护——把 JS/CSS 的缓存策略改成 CacheFirst,防止旧缓存被坏数据污染:

旧 SW 缓存旧 index.html → 旧 index.html 引用旧 JS chunk
→ 旧 JS 已被新部署删除 → nginx 返回 index.html (text/html)
→ 浏览器报 MIME type 错误 / SPA 找不到路由 → 黑屏

新 SW 激活后(永久生效):

index.html 每次都从服务器获取最新版 → 引用的 JS 一定在服务器上 → 不再有 MIME type 错误
/shortLink 不被拦截 → 走服务器 → 正确重定向 → 不再黑屏
过渡期(第一次访问,旧 SW 还在):

可能还会短暂出现一次错误
新 SW 后台安装激活后,下一次访问就正常了
部署后务必验证
无痕窗口打开 https://new.br-baby.com/sw.js,搜索确认:

skipWaiting 存在
denylist 存在
CacheFirst 存在(不是 StaleWhileRevalidate)
如果搜不到,说明 部署没有更新 sw.js 文件——问题就出在部署流程上

无痕 MIME 错误也是同理:无痕没有 SW 缓存 → 所有请求走服务器/CDN → 如果 CDN 缓存了旧版本的 index.html,或者某些 JS chunk 文件不存在,nginx 返回 index.html(text/html)→ 报 MIME 错误。

核心问题
你们的部署流程或 CDN 可能有以下问题之一:

CDN 缓存了旧文件 — 部署后 CDN 没有刷新缓存,用户拿到的是 CDN 里的旧内容
部署时删除了旧文件但 CDN 还在缓存 — 新 JS chunk 可能还没同步到 CDN
sw.js / index.html 被 CDN 长时间缓存 — 这两个文件绝对不能设长缓存

新部署 → 旧 JS chunk 被删除 → CDN/浏览器还缓存着旧 index.html
→ 旧 index.html 引用旧 chunk URL → 服务器找不到 → nginx try_files 返回 index.html
→ 浏览器收到 text/html 当作 JS 解析 → MIME type 错误

手动清 CDN 没用的原因:CDN 清了,但 index.html 本身没设 Cache-Control: no-cache,CDN 再次缓存后可能又拿到旧版(或中间有多层缓存)。

前端兜底:检测 chunk 加载失败自动刷新
我加一段脚本到 index.html,检测到 JS/CSS 加载失败时自动刷新一次(30秒内只允许一次,防死循环):

30 秒内最多刷新一次,不会死循环。用户最多看到一次白屏闪烁就恢复正常。

但根本问题还是 CDN
前端兜底只能"自动帮用户刷新一次",真正要杜绝这个问题,需要运维在 CDN 上配置:

另外,部署时不要立刻删除旧的 assets/ 文件,保留至少 1-2 天。这样即使有用户拿到了旧 index.html,旧 chunk 文件还在服务器上,就不会报 MIME 错误。

你可以把这个表格转发给运维,让他们在 CDN 管理后台对应路径设置缓存规则。这是最彻底的解决方案

每次 npm run build 都会生成带 content hash 的 JS/CSS 文件,比如:

dist/assets/Home.vue-B0y0uSFv.js ← 这次构建
dist/assets/Home.vue-DFNmhRYU.js ← 上次构建
dist/assets/Home.vue-DmItTU8E.js ← 上上次构建
同一个组件每次构建的 hash 都不同(B0y0uSFv vs DFNmhRYU)。

当你部署新包时,如果部署脚本是先清空 dist/ 目录再复制新文件,那上次构建的 Home.vue-DFNmhRYU.js 就被删了。

问题就出在这:如果有用户的浏览器/CDN 还缓存着旧的 index.html,那个旧 index.html 里写的是

建议的部署方式
部署时不要删旧文件,只追加新文件。过 1-2 天后再清理旧文件。这样新旧 index.html 引用的 chunk 都能找到:

❌ 当前方式(猜测)

rm -rf /www/dist/* ← 旧文件全删
cp -r dist/* /www/dist/ ← 只有新文件

✅ 建议方式

cp -r dist/* /www/dist/ ← 新旧文件共存,旧 index.html 的 chunk 也能找到

1-2天后再删超过2天的旧 assets

find /www/dist/assets -mtime +2 -delete

这样配合我们前端做的 SW 改动(denylist + controllerchange + chunk 加载失败自动刷新),MIME type 错误就能彻底杜绝了。

Feedback submitted

怎么做到新旧文件共存的,他不是同一个名字吗?不是会报错重名?我看后台是直接把dist文件解压到对应的文件夹里面去的

不会重名的。每次构建的文件名带了不同的 hash,它们是不同的文件:

上次构建:Home.vue-DFNmhRYU.js
这次构建:Home.vue-B0y0uSFv.js
这两个文件名不同,放在同一个文件夹里不会冲突。

你们的部署流程
你说"直接把 dist 解压到对应文件夹"——关键问题是:解压之前有没有先清空目标文件夹?

方式 效果
先清空再解压 旧的 Home.vue-DFNmhRYU.js 被删了,只剩新的 Home.vue-B0y0uSFv.js。如果有用户还在用旧 index.html,引用的旧文件找不到 → MIME 错误
直接解压覆盖(不清空) 新旧文件共存。旧 index.html 引用旧 hash → 文件还在 → 正常。新 index.html 引用新 hash → 也正常
index.html 和 sw.js 这种不带 hash 的文件会被直接覆盖成新版(因为同名),这正是我们需要的。

带 hash 的 JS/CSS 文件因为名字不同,解压时只是新增,不会覆盖旧的。

所以你可以让运维把"先清空目标文件夹"这一步去掉,改成直接解压覆盖。然后定期(比如每周)清理一下超过几天的旧 assets 文件就行。这样 MIME type 错误就不会再出现了。

他是直接先删除,然后再解压的

那就是这个原因了。先删除再解压导致旧 chunk 文件丢失,任何还在用旧 index.html 的用户就会报 MIME type 错误。

你让运维改成直接解压覆盖,不要先删除就行了。这是最简单也最彻底的方案。

如果运维担心旧文件堆积占磁盘,可以加一个定时清理脚本,比如每天删除 3 天前的 assets 文件:

bash

定时任务:清理 3 天前的旧 JS/CSS chunk

find /path/to/dist/assets -type f -mtime +3 -delete
配合我们在前端做的这些改动(chunk 加载失败自动刷新、SW denylist 禁用导航缓存),部署后 MIME type 错误就能彻底消除了。

前端这边的改动已经全部完成并构建好了。剩下的两项(#4 和 #5)需要运维配合处理,你可以把这两点转给运维:

部署方式:把"先删除目标文件夹再解压"改成"直接解压覆盖",定期清理 3 天前的旧 assets
CDN 缓存:index.html 和 sw.js 设置 no-cache 或 max-age=60,assets/* 可以设长缓存

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐