This is an automated email from the ASF dual-hosted git repository. moonming pushed a commit to branch fix/chunk-load-reload in repository https://gitbox.apache.org/repos/asf/apisix-website.git
commit 8237e8b23033daf6fcc43dc8f5138af8707e8edc Author: Ming Wen <[email protected]> AuthorDate: Mon Jun 29 12:07:37 2026 +0800 fix(website): auto-recover from stale code-split chunks after redeploy The site is rebuilt and republished frequently (every push to master plus a daily 05:00 UTC cron). Each build emits new content-hashed JS/CSS chunks and the publish step replaces the previous build wholesale, so a browser still on an older page can request a chunk hash that no longer exists on the CDN. Docusaurus client-side navigation then fails with a ChunkLoadError and renders the 404 page, even though a hard reload of the same URL serves fine. This is the "first visit 404, works after refresh" behavior users have reported. Add a shared client module (config/chunkReload.js) registered on all four Docusaurus instances (website, doc, blog/en, blog/zh). It listens for a ChunkLoadError via window 'error'/'unhandledrejection' and recovers with a single hard reload, which re-fetches the page against the current build. A sessionStorage guard prevents a reload loop when the chunk is genuinely unavailable (e.g. the browser is offline). --- blog/en/docusaurus.config.js | 4 +++ blog/zh/docusaurus.config.js | 4 +++ config/chunkReload.js | 79 ++++++++++++++++++++++++++++++++++++++++++++ doc/docusaurus.config.js | 4 +++ website/docusaurus.config.js | 4 +++ 5 files changed, 95 insertions(+) diff --git a/blog/en/docusaurus.config.js b/blog/en/docusaurus.config.js index 93003060185..8528c0bffc8 100644 --- a/blog/en/docusaurus.config.js +++ b/blog/en/docusaurus.config.js @@ -54,6 +54,10 @@ module.exports = { }, }, }, + clientModules: [ + // Recover from stale code-split chunks after a redeploy (see config/chunkReload.js). + require.resolve('../../config/chunkReload.js'), + ], presets: [ [ '@docusaurus/preset-classic', diff --git a/blog/zh/docusaurus.config.js b/blog/zh/docusaurus.config.js index 23d6b7b915b..e5cc976ba45 100644 --- a/blog/zh/docusaurus.config.js +++ b/blog/zh/docusaurus.config.js @@ -55,6 +55,10 @@ module.exports = { }, }, }, + clientModules: [ + // Recover from stale code-split chunks after a redeploy (see config/chunkReload.js). + require.resolve('../../config/chunkReload.js'), + ], presets: [ [ '@docusaurus/preset-classic', diff --git a/config/chunkReload.js b/config/chunkReload.js new file mode 100644 index 00000000000..1bcbced60a0 --- /dev/null +++ b/config/chunkReload.js @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Recover from stale code-split chunks after a redeploy. + * + * The site is rebuilt and republished frequently (every push to master plus a + * daily cron). Each build emits content-hashed JS/CSS chunks, and the publish + * step replaces the previous build wholesale, so a browser that is still on an + * older page may request a chunk hash that no longer exists on the CDN. When + * that happens during client-side navigation, Docusaurus surfaces a + * ChunkLoadError and renders the 404 page even though a hard reload of the same + * URL serves fine (the server always returns the current build's HTML). + * + * This client module detects that specific failure and recovers with a single + * hard reload, which re-fetches the page against the current build. A + * sessionStorage guard ensures a genuinely-missing chunk (e.g. the browser is + * offline) cannot trigger a reload loop. + */ + +const RELOAD_GUARD_KEY = 'apisix-chunk-reload-at'; +const GUARD_WINDOW_MS = 10000; +const CHUNK_ERROR_RE = /Loading (?:CSS )?chunk [\w-]+ failed|Loading chunk \d+ failed|ChunkLoadError|dynamically imported module/i; + +function isChunkLoadError(error) { + if (!error) { + return false; + } + if (error.name === 'ChunkLoadError') { + return true; + } + const message = typeof error === 'string' ? error : error.message; + return typeof message === 'string' && CHUNK_ERROR_RE.test(message); +} + +function reloadOnce() { + try { + const now = Date.now(); + const last = Number(window.sessionStorage.getItem(RELOAD_GUARD_KEY)) || 0; + // Already reloaded within the guard window: the chunk is really gone + // (offline, or purged for good). Stop so we don't loop, and let the + // 404 page render instead. + if (now - last < GUARD_WINDOW_MS) { + return; + } + window.sessionStorage.setItem(RELOAD_GUARD_KEY, String(now)); + } catch (e) { + // sessionStorage unavailable (private mode, blocked cookies): fall through + // and reload once rather than leaving the user on a broken page. + } + window.location.reload(); +} + +if (typeof window !== 'undefined') { + window.addEventListener('error', (event) => { + if (isChunkLoadError(event && event.error)) { + reloadOnce(); + } + }); + window.addEventListener('unhandledrejection', (event) => { + if (isChunkLoadError(event && event.reason)) { + reloadOnce(); + } + }); +} diff --git a/doc/docusaurus.config.js b/doc/docusaurus.config.js index ef8c7e8d008..e3692ef9a5f 100644 --- a/doc/docusaurus.config.js +++ b/doc/docusaurus.config.js @@ -58,6 +58,10 @@ module.exports = { onBrokenLinks: 'log', onBrokenMarkdownLinks: 'warn', noIndex: false, + clientModules: [ + // Recover from stale code-split chunks after a redeploy (see config/chunkReload.js). + require.resolve('../config/chunkReload.js'), + ], presets: [ [ '@docusaurus/preset-classic', diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index ec15903c283..2bde8acbde2 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -53,6 +53,10 @@ module.exports = { onBrokenLinks: 'log', onBrokenMarkdownLinks: 'warn', noIndex: false, + clientModules: [ + // Recover from stale code-split chunks after a redeploy (see config/chunkReload.js). + require.resolve('../config/chunkReload.js'), + ], presets: [ [ '@docusaurus/preset-classic',
