问题描述
项目部署更新后,用户反应长时间白屏,多次刷新后仍然白屏。
问题排查
用户那边多次刷新后仍然白屏无法显示,首先可以排除网络问题导致的加载缓慢,其次部署后我访问是正常的,也可以排除代码报错导致的白屏原因(react项目若没有做错误边界处理,JS发生错误也会使其白屏), 那么问题其实很明显了就是资源文件的错误加载导致无法正常渲染。
解决方案
注意: 一般普通用户的浏览器缓存是默认开启的,开发中我们防止缓存带来的更新延时,一般会自行关闭,但用户不是开发,是无法做到让每个用户去关闭缓存的,也不建议关闭缓存,因为缓存可以加快页面加载速度,提升用户体验,所以我们需要找到一种方法,既能保证用户访问时加载最新的资源文件,又能保证用户访问时加载的index.html是最新的,而不是缓存中的index.html。
- 利用nginx禁止index.html缓存,因为index.html是入口文件,若缓存了,那么即使资源文件更新了,用户访问的也是旧的资源文件,依旧导致白屏。
// nginx配置
server {
location / {
# 对 HTML 文件设置不缓存
if ($request_filename ~* .*\.(html|htm)$) {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
}
# 带哈希的静态资源长期缓存
if ($request_filename ~* .*\.(css|js|png|jpg|jpeg|gif|ico|webp|svg)$) {
expires 365d;
add_header Cache-Control "public, immutable";
}
try_files $uri $uri/ /index.html;
}
}
// vite.config.ts
build: {
rollupOptions: {
output: {
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
},
},
},
},
这个方法是最高效的,即保留了了浏览器缓存,又避免了index.html缓存带来的问题。
- 奈何我司运维不肯配合,只能采用第二种方案,在代码中监听资源文件的加载情况,若加载失败则强制刷新并清空缓存。
注意: 这里的刷新不是简单的刷新页面,而是强制刷新,即强制清除缓存,重新加载资源文件。
强制刷新清空缓存有以下几种方式
一. 利用window.location.reload(true)方法,当设置为'true'时,浏览器会绕过缓存,从服务器重新加载页面,不过这个参数存在浏览器兼容问题,可以排除。
二. 利用meta的特性, 但是某些浏览器可能不完全遵循 HTML 中的 http-equiv 缓存指令,服务器响应头始终优先级更高。
<!-- 在 HTML 头部添加 -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
三. 最后,最简单请求的URL后面加上时间戳,这样每次请求的URL都是不同的,浏览器就不会缓存了。
window.location.href = window.location.href.split('?')[0] + '?ts=' + Date.now();
完整代码
// App.tsx
...
import { useEffect } from 'react';
function App() {
// 监听白屏
// 1. 利用addEventListener监听error事件,当资源文件加载失败时,强制刷新页面
useEffect(() => {
window.addEventListener(
'error',
(event) => {
const target = event.target as Element | null;
if (target && (target.tagName === 'SCRIPT' || target.tagName === 'LINK')) {
window.location.href = window.location.href.split('?')[0] + '?ts=' + Date.now();
}
},
true,
);
// 2.也可以利用性能监控这个API,监听资源加载情况,当资源加载失败时,强制刷新页面
// const resourceObserver = new PerformanceObserver((list) => {
// list.getEntries().forEach((entry) => {
// console.log(entry, 'eeeee');
// if (entry.entryType === 'resource' && entry.duration === 0 && entry.transferSize === 0) {
// window.location.href = window.location.href.split('?')[0] + '?ts=' + Date.now();
// }
// });
// });
// // 启动监听
// resourceObserver.observe({ entryTypes: ['resource'] });
return () => window.removeEventListener('error', () => {});
}, []);
return (
...
);
}
export default App;