Flutter Web发布后缓存未清除,旧内容残留问题求助
Flutter Web 部署后浏览器缓存无法清除,用户无法看到更新内容
问题核心
部署Flutter Web应用后,浏览器持续加载旧的JavaScript、CSS、图片等资源,不会请求服务器上的新版本内容,导致用户看不到应用更新。
已尝试的解决方法
- 给URL脚本添加版本化查询参数(如
?v=1.0) - 在HTML中编写缓存清除脚本,但均未生效
服务器环境
Apache / cPanel
现有HTML代码
<!DOCTYPE html> <html> <head> <!-- If you are serving your web app in a path other than the root, change the href value below to reflect the base path you are serving from. The path provided below has to start and end with a slash "/" in order for it to work correctly. For more details: * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base This is a placeholder for base href that will be replaced by the value of the `--base-href` argument provided to `flutter build`. --> <base href="$FLUTTER_BASE_HREF"> <meta charset="UTF-8"> <meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta name="description" content="This Flutter project is about a fruit supermarket side admin"> <!-- iOS meta tags & icons --> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-title" content="shndz_panel"> <link rel="apple-touch-icon" href="icons/Icon-192.png"> <!-- Favicon --> <link rel="icon" type="image/png" href="favicon.png"/> <title>shndz</title> <link rel="manifest" href="manifest.json"> <script> // The value below is injected by flutter build, do not touch. const serviceWorkerVersion = {{flutter_service_worker_version}}; </script> </head> <body> <script src="flutter_bootstrap.js" async></script> <!--<script src="flutter_bootstrap.js" async></script>--> <script> {{flutter_js}} {{flutter_build_config}} function launchFlutter() { // Download main.dart.js _flutter.loader.load({ serviceWorker: { serviceWorkerVersion: serviceWorkerVersion, }, onEntrypointLoaded: function(engineInitializer) { engineInitializer.initializeEngine().then(function(appRunner) { appRunner.runApp(); }); } }); } window.addEventListener('load', function(ev) { if ('serviceWorker' in navigator) { // getting rid of undesired to fetch remote version.json file updated var seconds = new Date().getTime(); var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", '/version.json?v=' + seconds, true); xmlhttp.addEventListener('load', function() { if (xmlhttp.status == 200) { var buildNumber = xmlhttp.responseText; console.log('remote version is ' + buildNumber); var currentBuildNumber = window.localStorage.getItem('buildNumber'); console.log('local version is ' + currentBuildNumber); // clear worker cache if remote and local version are different if (currentBuildNumber != buildNumber) { console.log('App update is necessary. Clearing service workers cache'); caches.delete('flutter-app-manifest'); caches.delete('flutter-temp-cache'); caches.delete('flutter-app-cache'); // store new version number window.localStorage.setItem('buildNumber', buildNumber); } else { console.log('App is up to date'); } } launchFlutter(); }); window.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { return caches.delete(cacheName); }) ); }) ); window.clients.claim(); xmlhttp.addEventListener('error', function() { launchFlutter(); }); xmlhttp.addEventListener('abort', function() { launchFlutter(); }); xmlhttp.addEventListener('timeout', function() { launchFlutter(); }); xmlhttp.send(); } else { console.log('Service worker not found. Continue app loading.'); launchFlutter(); } }); </script> </body> </html>
Flutter 版本信息

解决方案
1. 修复HTML脚本语法错误
你当前的脚本存在嵌套错误:activate事件监听器写在了load事件回调内部且未正确闭合,导致逻辑执行异常。修正后的核心逻辑如下:
window.addEventListener('load', function(ev) { if ('serviceWorker' in navigator) { // 带时间戳请求版本文件,避免缓存 var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", '/version.json?v=' + new Date().getTime(), true); xmlhttp.addEventListener('load', function() { if (xmlhttp.status == 200) { var remoteVersion = xmlhttp.responseText.trim(); var localVersion = window.localStorage.getItem('buildNumber'); if (localVersion != remoteVersion) { console.log('检测到更新,清除所有缓存'); // 清除所有缓存并注销旧Service Worker Promise.all([ caches.keys().then(keys => Promise.all(keys.map(k => caches.delete(k)))), navigator.serviceWorker.getRegistrations().then(regs => regs.forEach(r => r.unregister())) ]).then(() => { window.localStorage.setItem('buildNumber', remoteVersion); // 强制刷新页面加载新资源 window.location.reload(true); }); } else { console.log('应用已是最新版本'); launchFlutter(); } } else { launchFlutter(); } }); xmlhttp.addEventListener('error', launchFlutter); xmlhttp.addEventListener('abort', launchFlutter); xmlhttp.addEventListener('timeout', launchFlutter); xmlhttp.send(); } else { console.log('不支持Service Worker,直接加载应用'); launchFlutter(); } }); // Service Worker激活时自动清除旧缓存 navigator.serviceWorker.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.filter(name => name.startsWith('flutter-')).map(name => caches.delete(name)) ); }).then(() => window.clients.claim()) ); });
2. 配置Apache缓存策略(通过.htaccess)
在项目根目录创建或修改.htaccess文件,设置精准的缓存规则:
# 禁止缓存HTML入口文件,确保每次请求最新版本 <FilesMatch "\.(html|htm)$"> FileETag None Header unset ETag Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate" Header set Pragma "no-cache" Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT" </FilesMatch> # Flutter编译后的静态资源已带哈希值,可长期缓存 <FilesMatch "\.(js|css|png|jpg|jpeg|gif|ico|svg)$"> Header set Cache-Control "max-age=31536000, public, immutable" </FilesMatch>
Flutter Web编译时会自动给静态资源文件名添加唯一哈希(如main.dart.js变为main.abc123.dart.js),配合上述规则,浏览器只会缓存带哈希的资源,而入口HTML强制不缓存,确保每次加载最新的资源引用。
3. 利用Flutter内置的Service Worker版本管理
Flutter已自动注入serviceWorkerVersion变量,可直接用它做版本校验,无需额外的version.json:
window.addEventListener('load', function(ev) { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('flutter_service_worker.js') .then(registration => { // 监听Service Worker更新 registration.addEventListener('updatefound', () => { const newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { if (confirm('应用有更新,是否立即刷新?')) { window.location.reload(); } } }); }); // 检查是否有等待激活的新版本 if (registration.waiting) { if (confirm('应用有更新,是否立即刷新?')) { registration.waiting.postMessage({ type: 'SKIP_WAITING' }); window.location.reload(); } } }); } launchFlutter(); });
同时在flutter_service_worker.js中添加消息监听(若生成的文件中没有):
self.addEventListener('message', (event) => { if (event.data?.type === 'SKIP_WAITING') { self.skipWaiting(); } });
4. 确保构建命令正确
每次部署前执行标准构建命令,指定部署路径:
flutter build web --release --base-href="/你的应用路径/"
替换/你的应用路径/为实际部署的路径,避免资源路径错误导致加载旧文件。
内容的提问来源于stack exchange,提问作者Amin Azizzadeh
相关产品推荐
相关产品推荐

