前端打包后白屏不报错:从 source-map、MIME、路由 base 到 chunk 加载失败的全链路排查

前言

你一定遇到过这种场景:本地 npm run dev 跑得好好的,npm run build 也没报错,把 dist/ 丢到 Nginx、对象存储或 CDN 上,打开页面一看------白屏,控制台一片干净,连个红色错误都没有。

更诡异的是,有些时候控制台真的什么都不报,但页面就是出不来;有些时候刷新一下能进,深链一访问又白屏;还有些时候上线十分钟,用户群里就开始反馈「页面打不开了」,你打开自己电脑却一切正常。

这种「本地绿灯、线上白屏」的问题,排查难度往往比明确的报错更高,因为它把真正的错误藏在了 source-map、MIME、路由 base、chunk hash 这些「非代码逻辑」的工程链路里。本文按真实的排查链路,从 DevTools 三件套开始,一层一层把白屏的原因和验证方法讲清楚,并给出可直接复用的 Vite/Webpack/Nginx 配置和兜底代码。


一、白屏排查的第一性原理:先打开 DevTools 这三个面板

白屏一旦发生,第一反应不应该是「回去改代码」,而是打开浏览器 DevTools 的这三个面板按顺序看。80% 的线上白屏可以在 30 秒内定位到链路层。

1. Console:先确认「是不是真的没报错」

很多人说「没报错」,其实只是没看到红色。Console 面板要重点检查三件事:

  • 过滤级别 :默认会隐藏 Verbose 级别,把日志级别切到 Verbose,再确认一次有没有被降级的警告。
  • 过滤上下文 :如果你在 Console 顶部选择了某个 iframe 或 worker,主页面的错误可能被过滤掉,切回 top
  • 按类型过滤 :只勾选 Errors,看是否真的是 0 条。

常见的「看起来不报错」其实是这种情况:

text 复制代码
Failed to load resource: the server responded with a status of 404 (Not Found)

这条是网络层的 404,默认在 Console 里是「小黄色图标 + 一行灰字」,很容易被忽略。如果你看了 Console 觉得没报错,先去 Network 再确认一次。

2. Network:看资源是否真的加载到了

切到 Network 面板,刷新页面,勾选 Disable cache,把过滤切到 JSFetch/XHR,重点看:

  • Status 列200 才是正常,404(找不到资源)、403(权限/对象存储策略)、304(协商缓存正常)、500(服务端炸了)都要排查。
  • Type 列 :这条很多人忽略,但非常关键。.js 文件的 Type 应该是 scriptfetch,如果显示成 documenttext/plainapplication/octet-stream,浏览器会拒绝执行,详见第五节。
  • Initiator 列 :告诉你这个资源是被谁触发的,比如 index.html、某个动态 import(),能帮你判断是不是路由懒加载的 chunk。
  • Time / Waterfall:是不是某些资源阻塞过久导致超时。

3. Sources:确认「加载下来的代码」是不是你以为的那份

Sources 面板不只是看代码,它有两个关键作用:

  • 左侧文件树:能不能看到你期望的 JS 文件。如果 Network 是 200 但 Sources 里看不到,多半是 MIME 拦截或 CSP 拦截,资源被「加载但未执行」。
  • Page 源码对照 :右键页面 → View page source,确认服务端返回的 index.html 是不是最新的(有没有旧的 <script> 引用),这一步对第七节的 ChunkLoadError 非常关键。

一句话总结这三个面板的分工:Console 看现象、Network 看资源、Sources 看结果。任何一个白屏问题,都先用这三个面板把链路走一遍,再决定下一步。


二、「看起来不报错」的真相:source-map 是怎么把错误藏起来的

1. source-map 到底是什么

source-map 是打包产物(压缩混淆后的 JS)和源码之间的「映射表」,文件通常是 xxx.js.map,JS 文件末尾通过一行注释关联:

javascript 复制代码
//# sourceMappingURL=xxx.js.map

浏览器(DevTools)看到这一行,会去加载 .map 文件,把压缩堆在一起的代码「还原」成你写的源码,这样你打断点、看错误堆栈时,定位到的是 src/views/Home.vue:12 而不是 dist/assets/index-a3f9b1.js:1:4823

2. source-map 缺失如何让你「看不到真错」

线上白屏里最坑的一种情况:错误确实发生了,但堆栈指向的是压缩后的 dist/assets/chunk-xxxx.js:1:9281,你点进去看到的是一坨挤在一起的代码,完全不知道是哪个组件炸了。

这种情况下,不是没报错,是报错你看不懂 。如果你恰好把 source-map 关掉了(很多团队的默认做法),又没有把 .map 单独上传到错误监控平台,那线上错误就会变成一个黑盒。

3. source-map 的几种模式怎么选

以 Vite 为例,build.sourcemap 支持以下几种值,含义差异要分清楚(Webpack 对应的 devtool 选项类似):

Vite build.sourcemap 行为 适用场景
false(默认) 不生成 source-map 纯内网工具、完全不在意线上报错
true 生成 .map 文件,并在 JS 末尾写入 sourceMappingURL 本地、预发环境排查
'inline' .map 以 DataURL 内嵌进 JS,不生成独立文件 体积敏感、懒得分发 .map
'hidden' 生成 .map 文件,但 写入 sourceMappingURL 线上推荐:上传到监控平台,浏览器不暴露
'nosources' 生成 .map 但不含源码内容(只有行列映射) 想给监控平台用但不泄露源码

Vite 配置示例:

ts 复制代码
// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    // 推荐:线上用 hidden,配合错误监控平台上传 .map
    sourcemap: 'hidden',
  },
})

Webpack 对应写法(devtool 是一个组合选项):

js 复制代码
// webpack.config.js
module.exports = {
  // 等价于 hidden-source-map:生成 .map 但不写 sourceMappingURL
  devtool: 'hidden-source-map',
  // 想保留映射但不暴露源码:nosources-source-map
  // devtool: 'nosources-source-map',
}

4. 线上隐藏 source-map 的正确姿势

这里有一个常见的概念混淆需要强调:「隐藏」不等于「删除」

  • 隐藏(hidden) :生成 .map 文件,但 JS 产物里不写 sourceMappingURL,浏览器不会主动加载,普通用户在 Sources 里看不到源码。文件本身仍然存在,你可以上传到 Sentry / 自建错误监控 / 只在内网可访问的对象存储桶里。
  • 删除 :连 .map 文件都不生成,你彻底失去线上错误的源码定位能力。

线上推荐的链路是:

  1. build.sourcemap: 'hidden',构建出 .map 文件。
  2. .map 文件上传到错误监控平台(Sentry、Bugsnag、自建平台都支持)。
  3. 不要.map 文件部署到对公网开放的静态服务器上。

Nginx 层面可以再加一道保险,直接禁止 .map 文件对外访问:

nginx 复制代码
location ~ \.map$ {
    deny all;
    return 404;
}

这样既能在监控平台里看到带源码位置的堆栈,又不会把源码暴露给公网。


三、资源 404:publicPath / base 配错了

如果 Network 面板里看到一堆 .js / .css 都是 404,几乎可以肯定是 资源路径 配错了。这是白屏里最高频的一类。

1. 相对路径 ./ 的坑

Vite 默认 base: './',Webpack 默认 publicPath: 'auto',它们都想生成「相对路径」的资源引用。相对路径在 根路径部署 时没问题,一旦你部署到子路径就会出事。

假设你的应用部署在 https://example.com/app/,但你的 index.html 里生成的引用是:

html 复制代码
<script type="module" crossorigin src="./assets/index-a3f9b1.js"></script>

用户访问 https://example.com/app/ 时能进首页,因为相对路径会解析成 https://example.com/app/assets/index-a3f9b1.js。但如果你用了 history 路由,用户访问 https://example.com/app/user/profile 时刷新,浏览器会去请求:

text 复制代码
https://example.com/app/user/profile/assets/index-a3f9b1.js

这个路径当然不存在,于是 404,白屏。

2. 子路径部署的正确配置

部署在 /app/ 子路径下,要把 base / publicPath 显式写死:

ts 复制代码
// vite.config.ts
export default defineConfig({
  base: '/app/',  // 注意首尾都要有斜杠
})
js 复制代码
// webpack.config.js (webpack 5)
module.exports = {
  output: {
    publicPath: '/app/',
  },
}

同时前端路由也要同步配置 base:

js 复制代码
// Vue Router 4
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory('/app/'),
  routes: [...],
})

// React Router 6
<BrowserRouter basename="/app/">
  <App />
</BrowserRouter>

3. CDN 部署

如果你的静态资源走 CDN,HTML 还在自己服务器上,base 要写成完整的 CDN 地址:

ts 复制代码
// vite.config.ts
export default defineConfig({
  base: 'https://cdn.example.com/my-app/',
})

这样生成的引用是:

html 复制代码
<script type="module" crossorigin src="https://cdn.example.com/my-app/assets/index-a3f9b1.js"></script>

注意 CDN 路径末尾的斜杠不能省,否则会拼成 https://cdn.example.com/assets/...


四、刷新就白屏:history 路由 404 与 Nginx try_files

这一节要和第三节区分开:第三节是 资源 404 (找不到 .js),这一节是 HTML 404(路由路径找不到),两者现象类似但根因不同。

1. 为什么首页能进、刷新就白屏

前端使用了 HTML5 history 路由(createWebHistory / BrowserRouter)后,URL 长这样:

text 复制代码
https://example.com/user/profile

访问首页时,用户其实只请求了 https://example.com/,Nginx 返回 index.html,前端 JS 接管路由后通过 history.pushState 把 URL 改成 /user/profile这一次没有发生新的 HTTP 请求

但如果用户在 /user/profile 上按 F5 刷新,或者直接把链接发给同事,浏览器会真的去请求 https://example.com/user/profile,Nginx 在磁盘上找不到这个文件,返回 404,页面就白屏了。

2. Nginx 最小可用配置:try_files

解决方法是让 Nginx 在找不到磁盘文件时,回退到 index.html,把路由交给前端处理:

nginx 复制代码
server {
    listen 80;
    server_name example.com;
    root /usr/share/nginx/html;
    index index.html;

    location / {
        # 关键:磁盘找不到的路径都回退到 index.html
        try_files $uri $uri/ /index.html;
    }

    # 静态资源缓存(可选)
    location ~* \.(?:js|css|woff2?|png|jpe?g|gif|svg|ico)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

如果你部署在子路径 /app/,对应改成:

nginx 复制代码
location /app/ {
    # alias 把 URL 前缀剥掉映射到磁盘目录
    alias /usr/share/nginx/html/app/;
    try_files $uri $uri/ /app/index.html;
}

注意 try_files 的回退路径要和 base 一致:base: '/app/',回退也必须是 /app/index.html,否则会陷入 404 死循环。

对象存储(OSS / S3 / COS)也有类似配置,例如阿里云 OSS 的「静态页面」里要开启「默认首页」并配置「404 文件」回退到 index.html,原理和 Nginx 的 try_files 一致。


五、MIME 类型错误:.js 被当成文本,浏览器直接拒绝执行

这一类白屏最容易发生在 自己配 Nginx对象存储直传 的场景下,特征是 Network 面板显示 200 OK,但 Console 会报一条非常显眼的错:

text 复制代码
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/plain". Strict MIME type checking is enforced for module scripts per HTML spec.

或者:

text 复制代码
Refused to execute script from 'https://example.com/assets/index-a3f9b1.js' because its MIME type ('application/octet-stream') is not executable.

1. 为什么会有严格 MIME 检查

浏览器对 <script type="module">(也就是 Vite/现代打包工具默认输出的 ESM 模块)执行的是 严格 MIME 检查 :服务端返回的 Content-Type 必须是 application/javascripttext/javascriptapplication/ecmascript 中的某一个,否则直接拒绝执行。这是为了防止把 HTML、JSON、文本误当成脚本执行导致的安全问题。

注意:传统 <script src>(非 module)的 MIME 检查相对宽松,所以从 Webpack 老项目迁移到 Vite 后突然出现 MIME 报错,是非常典型的情况。

2. Nginx 里 types / mime.types 怎么配

Nginx 默认会在 http 块里 include mime.types;,这个文件定义了扩展名到 MIME 的映射。如果你自定义了一个 server 块却没继承 types,所有响应会回退到默认的 application/octet-stream,触发 MIME 报错。

正确的做法:

nginx 复制代码
http {
    include       mime.types;   # 必须包含
    default_type  application/octet-stream;

    server {
        listen 80;
        server_name example.com;
        root /usr/share/nginx/html;

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

如果 mime.types 里少了某些扩展名(比如 .mjs.woff2),手动补上:

nginx 复制代码
types {
    application/javascript js mjs;
    text/css              css;
    application/json      json;
    image/png             png;
    image/jpeg            jpeg jpg;
    font/woff2            woff2;
}

或者在 location 里直接强制 Content-Type(应急方案):

nginx 复制代码
location ~* \.mjs$ {
    default_type application/javascript;
}

3. 对象存储的 MIME 问题

dist/ 上传到 OSS/S3/COS 时,如果用了不正确的上传工具,扩展名的 MIME 可能不会被识别。比如用 aws s3 cp --content-type 上传时要显式指定,或者用 s3 sync --no-guess-mime-type 反而会丢失类型。建议上传时让工具根据扩展名自动推断,或后处理批量修正 .js / .mjs / .cssContent-Type


六、CORS 与 CSP:静态资源被浏览器拦下

1. 跨域 CDN 触发 CORS

当 HTML 和 JS 不在同一个源下(比如 HTML 在 example.com,JS 在 cdn.example.com 或第三方 CDN),浏览器会做 CORS 校验。对 <script type="module"> 来说,CDN 服务器必须在响应头里返回:

text 复制代码
Access-Control-Allow-Origin: *

否则 Console 会报:

text 复制代码
Access to script at 'https://cdn.example.com/assets/index-a3f9b1.js' from origin 'https://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

同时 HTML 里的 <script> 标签必须显式声明跨域:

html 复制代码
<script type="module" crossorigin src="https://cdn.example.com/.../index.js"></script>

Vite 通过 build.modulePreload.polyfill 和默认 crossorigin: 'credentials' 处理大部分场景,但你换成自建 CDN 时要自己检查响应头。

Nginx 加 CORS 头:

nginx 复制代码
location /assets/ {
    add_header Access-Control-Allow-Origin "*";
    add_header Access-Control-Allow-Methods "GET, OPTIONS";
}

2. CSP 拦截 script-src

如果你的站点配置了 Content-Security-Policy,比如:

text 复制代码
Content-Security-Policy: default-src 'self'; script-src 'self'

那么从 CDN 加载 JS 就会被拦,Console 报错:

text 复制代码
Refused to load the script 'https://cdn.example.com/assets/index.js' because it violates the following Content Security Policy directive: "script-src 'self'".

修复方法是把 CDN 域名加进 script-src

nginx 复制代码
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline';";

注意 CSP 是个白名单机制,调试时可以先用 Content-Security-Policy-Report-Only(只上报不拦截)观察影响范围,再切换到正式策略。


七、ChunkLoadError:部署后 hash 变了,老页面还在加载旧 chunk

这是用户反馈「页面打不开了」、但你打开自己电脑一切正常的最典型原因。

1. 两类根因

根因 A:用户浏览器里缓存了旧的 index.html,部署后新旧 hash 对不上。

Vite/Webpack 默认给每个 chunk 加上内容 hash,文件名形如 chunk-UserProfile-a3f9b1c2.js。你部署新版本后,新的 index.html 引用的是 chunk-UserProfile-b7e1d09.js。但如果用户的浏览器还持有旧版本的 index.html(缓存未过期),前端代码会按旧 HTML 里的引用去请求 chunk-UserProfile-a3f9b1c2.js,而服务器上这个文件已经被新版本覆盖或删除了,于是返回 404。

报错通常是:

text 复制代码
ChunkLoadError: Loading chunk app failed.
(missing: https://example.com/assets/app-a3f9b1c2.js)

或者 React 的 lazy 路由会抛:

text 复制代码
Uncaught (in promise) ChunkLoadError: Loading chunk X failed.

根因 B:静态资源未覆盖或 CDN 缓存未刷新。

部署时只更新了部分文件(比如 CI 漏传了某个 chunk),或者 CDN 节点回源时缓存了 404 响应,都会让用户拿到一个不存在的 chunk。

2. 解决思路:让 index.html 不被缓存

要避免根因 A,关键是让 index.html 永远走协商缓存或直接不缓存,每次都拿到最新版本:

nginx 复制代码
location = /index.html {
    add_header Cache-Control "no-cache, no-store, must-revalidate";
    add_header Pragma "no-cache";
    expires 0;
}

# 带 hash 的静态资源可以长缓存(因为内容变了文件名也变了)
location ~* \.(?:js|css|woff2?|png|jpe?g|gif|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

这样用户每次访问都拿到最新 HTML,引用的也就是最新的 chunk hash。

3. 兜底方案:ChunkLoadError 自动重试 + reload

即使用了上面的缓存策略,仍然会有边界情况(用户长时间挂着页面、灰度发布期间新旧版本混存)。可以通过监听 ChunkLoadError,自动 reload 一次让浏览器拿到新版本:

js 复制代码
// utils/chunkErrorHandler.ts
let hasReloaded = false

export function setupChunkErrorHandler() {
  // 方式 1:监听 window 的 unhandled error(Vite/原生 ESM 场景)
  window.addEventListener('error', (event) => {
    const target = event.target as HTMLElement | null
    // 资源加载失败(script/link/img)会触发 error 事件,但不冒泡到 window.onerror
    // 需要在 capture 阶段拦截
  }, true)

  // 捕获资源加载失败(link/script)
  window.addEventListener(
    'error',
    (event) => {
      const target = event.target as HTMLElement
      const isChunk =
        target instanceof HTMLScriptElement || target instanceof HTMLLinkElement
      if (isChunk && !hasReloaded) {
        hasReloaded = true
        // 强制跳过缓存重新加载
        window.location.reload()
      }
    },
    true, // 必须用 capture 阶段
  )

  // 方式 2:监听 unhandledrejection(webpack 的动态 import() 会 reject)
  window.addEventListener('unhandledrejection', (event) => {
    const reason = event.reason
    const isChunkError =
      reason?.name === 'ChunkLoadError' ||
      /Loading chunk .* failed/i.test(reason?.message || '')
    if (isChunkError && !hasReloaded) {
      hasReloaded = true
      // 带一个 query 标识,避免无限 reload
      const url = new URL(window.location.href)
      if (!url.searchParams.has('_reload')) {
        url.searchParams.set('_reload', '1')
        window.location.replace(url.toString())
      }
    }
  })
}

在入口文件调用:

js 复制代码
// main.ts / main.js
import { setupChunkErrorHandler } from './utils/chunkErrorHandler'
setupChunkErrorHandler()

Vue Router 还可以在路由懒加载的 onError 里兜一次:

js 复制代码
const router = createRouter({ ... })

router.onError((error) => {
  if (/Loading chunk .* failed|ChunkLoadError/.test(error.message)) {
    window.location.reload()
  }
})

React 的 React.lazy + Suspense 可以通过 ErrorBoundary 捕获并 reload:

jsx 复制代码
class ChunkErrorBoundary extends React.Component {
  state = { hasError: false }
  static getDerivedStateFromError(err) {
    const isChunk = err?.name === 'ChunkLoadError' || /Loading chunk/.test(err?.message || '')
    return { hasError: isChunk }
  }
  componentDidCatch(err) {
    if (this.state.hasError && !window.location.search.includes('_reload')) {
      window.location.reload()
    }
  }
  render() {
    return this.state.hasError ? null : this.props.children
  }
}

注意 reload 时一定要加 _reload 之类的标识位,否则一旦真的服务端有问题,会陷入无限刷新。


八、一份可直接照做的排查 SOP

在开始排查之前,先看这张「白屏全链路排查路径示意图」,它把 DevTools → source-map → publicPath/base → MIME → 路由 → chunk 的链路顺序串成了一条完整的判断路径,下方的清单表格也按这个顺序展开。

下面这张表可以直接当作排查清单,按「现象 → 可能原因 → 验证方法 → 修复」逐行往下走:

现象 可能原因 验证方法 修复
Console 完全空白,页面无任何渲染 JS 没加载,或加载了但被 MIME/CSP 拦截 Network 看 .js 是否 200,Type 是否为 script base / MIME / CSP(第三、五、六节)
Network 显示 .js 大量 404 publicPath / base 配错 index.html<script src> 的 URL 是否符合实际部署路径 base: '/app/',路由同步配 base(第三节)
首页能进,刷新 / 深链就白屏 history 路由 404 直接访问 xxx.com/user/profile 看 HTTP 状态 Nginx try_files ... /index.html(第四节)
Network 200 但 Console 报 MIME 错 Content-Type 不是 JS MIME 看响应头 Content-Type include mime.types; 或显式 default_type(第五节)
Console 报 CORS / CSP 错 CDN 跨域或 CSP 白名单没加 看响应头 Access-Control-Allow-Origin 和 CSP CDN 加 CORS 头,CSP script-src 加白名单(第六节)
部署后部分用户白屏,自己正常 ChunkLoadError,旧 HTML 缓存 让用户报错截图,看是否有 Loading chunk X failed index.html 不缓存 + 自动 reload 兜底(第七节)
报错堆栈指向压缩代码,看不懂 source-map 缺失或被删 .map 文件是否上传到监控平台 build.sourcemap: 'hidden',上传监控平台(第二节)

附一份「开箱即用」的最小 Nginx 配置,覆盖前四类问题:

nginx 复制代码
server {
    listen 80;
    server_name example.com;
    root /usr/share/nginx/html;
    index index.html;

    # 开启 gzip(可选)
    gzip on;
    gzip_types text/css application/javascript application/json image/svg+xml;

    # index.html 不缓存,保证用户每次拿到最新版本
    location = /index.html {
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        expires 0;
    }

    # 带 hash 的静态资源长缓存
    location ~* \.(?:js|mjs|css|woff2?|png|jpe?g|gif|svg|ico)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 不暴露 source-map
    location ~ \.map$ {
        deny all;
        return 404;
    }

    # history 路由回退
    location / {
        try_files $uri $uri/ /index.html;
    }
}

配合一份「不要忘」的 http 块:

nginx 复制代码
http {
    include       mime.types;
    default_type  application/octet-stream;
    # ... 其他全局配置
}

常见问题与避坑

  • 「我把 sourcemap 直接关了,是不是更安全?」 ------ 表面上是,但你失去了线上错误的源码定位能力,监控平台拿到的也是压缩堆栈,等于盲人摸象。推荐用 hidden 而不是 false,把 .map 单独上传到监控平台。
  • 「我把 .map 文件和 JS 一起部署上线了,会怎样?」 ------ 任何人在 DevTools 里都能看到你的源码。立即用 Nginx location ~ \.map$ { deny all; } 拦截,并把构建产物的 .map 改为只上传到监控平台。
  • 「相对路径 ./ 在某些环境能跑,是不是不用配 base?」 ------ 根路径部署下能跑,但用了 history 路由 + 子路径刷新时大概率炸。养成显式配置 base 的习惯,省得后面排查。
  • try_files 改了还是 404?」 ------ 九成是回退路径和 base 不一致。base: '/app/' 时回退也必须是 /app/index.html,写成 /index.html 就死循环。
  • 「MIME 报错只在 Vite 项目里出现,Webpack 老项目没事?」 ------ 因为 Vite 默认输出 ESM(<script type="module">),浏览器对 module 脚本执行严格 MIME 检查;Webpack 老项目通常用传统 script,检查较宽松。迁项目时要格外注意。
  • 「ChunkLoadError 自动 reload 会不会死循环?」 ------ 会,如果不加标识位。务必在 reload 前查 URL 是否已经带过 _reload,或用 sessionStorage 记录本次会话已经 reload 过一次。
  • 「对象存储上传后部分文件 404?」 ------ 检查上传工具是否漏传(特别是 assets/ 下的 chunk),或者对象存储的权限策略是否对某些路径返回了 403 而不是 404。

总结

白屏不报错之所以难排查,是因为真正的错误被工程链路上的多个环节层层包裹。本文按真实的排查顺序,把这些环节串成了一条链:

  • 先打开 Console / Network / Sources 三个面板,按 Console 看现象、Network 看资源、Sources 看结果的顺序,30 秒内就能定位到链路层;
  • source-map 决定你能不能读懂错误 ,推荐用 hidden 模式,生成 .map 上传监控平台,同时用 Nginx 拦截公网访问;
  • base / publicPath 决定资源 404 ,子路径、CDN 部署必须显式配置,前端路由的 base 要同步;
  • try_files 决定刷新白屏 ,history 路由下 Nginx 必须回退到 index.html,对象存储也有对应配置;
  • MIME 决定脚本能不能执行 ,ESM 模块的严格检查让 .js 被当文本时直接被拒,要确保 include mime.types;
  • CORS / CSP 决定跨域资源能否加载 ,CDN 要加 Access-Control-Allow-Origin,CSP 要补 script-src 白名单;
  • ChunkLoadError 是部署后用户白屏的高频原因 ,关键是让 index.html 不缓存,配合监听 error / unhandledrejection 做一次性的自动 reload 兜底。

把这些环节的配置和验证方法内化成排查清单,下次遇到「本地正常、线上白屏」时,就不用再凭感觉改代码了,按链路走一遍,基本都能定位到具体环节。

相关推荐
三84419 小时前
笔记:在同一个物理服务器上通过nginx的虚拟主机生成多个不同的web站点
java·前端·nginx
double_eggm19 小时前
uniapp.3
开发语言·前端·javascript
索西引擎20 小时前
【React】状态提升模式:设计原理、权衡分析与架构决策框架
前端·react.js·架构
胡萝卜术1 天前
力扣5. 最长回文子串
前端·javascript·面试
我星期八休息1 天前
网络编程—应用层HTTP协议
linux·运维·开发语言·前端·网络·网络协议·http
不好听6131 天前
前端路由完全指南(下篇):懒加载、History 栈与工程化实践
前端·react.js
圣光SG1 天前
Java Web入门基础知识笔记
java·前端·笔记
cyforkk1 天前
Vercel 绑定自定义域名极简配置指南
服务器·前端·网络