This is an automated email from the ASF dual-hosted git repository.
moonming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-website.git
The following commit(s) were added to refs/heads/master by this push:
new a23a848b43a fix(website): auto-recover the 404 page from stale builds
after redeploy (#2064)
a23a848b43a is described below
commit a23a848b43acb8224167f4028e243d2683db424f
Author: Ming Wen <[email protected]>
AuthorDate: Mon Jun 29 13:27:51 2026 +0800
fix(website): auto-recover the 404 page from stale builds after redeploy
(#2064)
The site is rebuilt and republished frequently (every push to master plus a
daily 05:00 UTC cron), and each build replaces the previous build's
content-hashed chunks. A browser still showing a page from an older build
holds
that build's route manifest; clicking through to a route that only exists
in a
newer build finds no client-side match and renders the 404 page, even
though a
hard load of the same URL is served fine (HTTP 200). This is the reported
"first visit 404, works after refresh" behavior.
The repo already swizzles a custom theme/NotFound in all three Docusaurus
instances (website, doc, blog). Add a guarded recovery there: when the 404
is
reached via a client-side navigation (the document the server returned was
for a
different path), reload once so the current build is fetched. Genuine 404s
--
the server returned this page directly -- are left untouched.
Detection uses PerformanceNavigationTiming to compare the document's load
path
with the current path, so real broken links do not trigger a wasted reload,
and
a per-path sessionStorage guard reloads at most once as a backstop.
---
blog/src/theme/NotFound/index.tsx | 138 +++++++++++++++++++++--------------
doc/src/theme/NotFound/index.tsx | 138 +++++++++++++++++++++--------------
website/src/theme/NotFound/index.tsx | 138 +++++++++++++++++++++--------------
3 files changed, 255 insertions(+), 159 deletions(-)
diff --git a/blog/src/theme/NotFound/index.tsx
b/blog/src/theme/NotFound/index.tsx
index c8a513b32b1..6a43297540d 100644
--- a/blog/src/theme/NotFound/index.tsx
+++ b/blog/src/theme/NotFound/index.tsx
@@ -1,6 +1,6 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import type { FC } from 'react';
-import React from 'react';
+import React, { useEffect } from 'react';
import Layout from '@theme/Layout';
import Head from '@docusaurus/Head';
import { translate } from '@docusaurus/Translate';
@@ -8,57 +8,89 @@ import Link from '@docusaurus/Link';
import style from './styles.module.scss';
import Fitty from './Fitty';
-const NotFound: FC = () => (
- <Layout
- title={translate({
- id: 'theme.NotFound.title',
- message: 'Page Not Found',
- })}
- >
- <Head>
- <meta name="robots" content="noindex,follow" />
- </Head>
- <main className={style.container}>
- <section>
- <Fitty tagName="h1" contentEditable>404</Fitty>
- <Fitty tagName="h2">
- Page Not Found
- </Fitty>
- </section>
- <p>
- We could not find what you were looking for.
- </p>
- <p>
- If you think this link should not be broken, please
- {' '}
- <Link
href="https://github.com/apache/apisix-website/issues/new/choose"
target="_blank" rel="noreferrer">submit an Issue</Link>
- .
- </p>
- <p>
- You can also open the
- {' '}
- <Link to="/">home page</Link>
- ,
- {' '}
- <Link to="/docs/">documentation</Link>
- ,
- {' '}
- <Link to="/blog/">blog</Link>
- , or
- {' '}
- <a
- role="button"
- href="#"
- onClick={() => {
- window?.history.back();
- }}
- >
- return to the source page
- </a>
- .
- </p>
- </main>
- </Layout>
-);
+const NotFound: FC = () => {
+ useEffect(() => {
+ // The site is rebuilt and republished frequently, and each build replaces
the
+ // previous one's content-hashed chunks. A page still open from an older
build
+ // can client-navigate to a route that is missing from its stale route
manifest
+ // and land here, even though the server serves that page fine. Recover
with a
+ // single hard reload so the current build is fetched.
+ //
+ // Only do this when the 404 surfaced during a client-side navigation, not
on
+ // the document the server returned directly — that is a genuine 404 and is
+ // left untouched. A per-path guard reloads at most once as a safety
backstop.
+ try {
+ if (typeof window === 'undefined' || !window.performance) {
+ return;
+ }
+ const [nav] = window.performance.getEntriesByType('navigation');
+ const { pathname } = window.location;
+ if (!nav || new URL(nav.name).pathname === pathname) {
+ return;
+ }
+ const guardKey = `apisix-404-reload:${pathname}`;
+ if (window.sessionStorage.getItem(guardKey)) {
+ return;
+ }
+ window.sessionStorage.setItem(guardKey, '1');
+ window.location.reload();
+ } catch {
+ // performance / sessionStorage / URL unavailable — leave the 404 as-is.
+ }
+ }, []);
+
+ return (
+ <Layout
+ title={translate({
+ id: 'theme.NotFound.title',
+ message: 'Page Not Found',
+ })}
+ >
+ <Head>
+ <meta name="robots" content="noindex,follow" />
+ </Head>
+ <main className={style.container}>
+ <section>
+ <Fitty tagName="h1" contentEditable>404</Fitty>
+ <Fitty tagName="h2">
+ Page Not Found
+ </Fitty>
+ </section>
+ <p>
+ We could not find what you were looking for.
+ </p>
+ <p>
+ If you think this link should not be broken, please
+ {' '}
+ <Link
href="https://github.com/apache/apisix-website/issues/new/choose"
target="_blank" rel="noreferrer">submit an Issue</Link>
+ .
+ </p>
+ <p>
+ You can also open the
+ {' '}
+ <Link to="/">home page</Link>
+ ,
+ {' '}
+ <Link to="/docs/">documentation</Link>
+ ,
+ {' '}
+ <Link to="/blog/">blog</Link>
+ , or
+ {' '}
+ <a
+ role="button"
+ href="#"
+ onClick={() => {
+ window?.history.back();
+ }}
+ >
+ return to the source page
+ </a>
+ .
+ </p>
+ </main>
+ </Layout>
+ );
+};
export default NotFound;
diff --git a/doc/src/theme/NotFound/index.tsx b/doc/src/theme/NotFound/index.tsx
index c8a513b32b1..6a43297540d 100644
--- a/doc/src/theme/NotFound/index.tsx
+++ b/doc/src/theme/NotFound/index.tsx
@@ -1,6 +1,6 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import type { FC } from 'react';
-import React from 'react';
+import React, { useEffect } from 'react';
import Layout from '@theme/Layout';
import Head from '@docusaurus/Head';
import { translate } from '@docusaurus/Translate';
@@ -8,57 +8,89 @@ import Link from '@docusaurus/Link';
import style from './styles.module.scss';
import Fitty from './Fitty';
-const NotFound: FC = () => (
- <Layout
- title={translate({
- id: 'theme.NotFound.title',
- message: 'Page Not Found',
- })}
- >
- <Head>
- <meta name="robots" content="noindex,follow" />
- </Head>
- <main className={style.container}>
- <section>
- <Fitty tagName="h1" contentEditable>404</Fitty>
- <Fitty tagName="h2">
- Page Not Found
- </Fitty>
- </section>
- <p>
- We could not find what you were looking for.
- </p>
- <p>
- If you think this link should not be broken, please
- {' '}
- <Link
href="https://github.com/apache/apisix-website/issues/new/choose"
target="_blank" rel="noreferrer">submit an Issue</Link>
- .
- </p>
- <p>
- You can also open the
- {' '}
- <Link to="/">home page</Link>
- ,
- {' '}
- <Link to="/docs/">documentation</Link>
- ,
- {' '}
- <Link to="/blog/">blog</Link>
- , or
- {' '}
- <a
- role="button"
- href="#"
- onClick={() => {
- window?.history.back();
- }}
- >
- return to the source page
- </a>
- .
- </p>
- </main>
- </Layout>
-);
+const NotFound: FC = () => {
+ useEffect(() => {
+ // The site is rebuilt and republished frequently, and each build replaces
the
+ // previous one's content-hashed chunks. A page still open from an older
build
+ // can client-navigate to a route that is missing from its stale route
manifest
+ // and land here, even though the server serves that page fine. Recover
with a
+ // single hard reload so the current build is fetched.
+ //
+ // Only do this when the 404 surfaced during a client-side navigation, not
on
+ // the document the server returned directly — that is a genuine 404 and is
+ // left untouched. A per-path guard reloads at most once as a safety
backstop.
+ try {
+ if (typeof window === 'undefined' || !window.performance) {
+ return;
+ }
+ const [nav] = window.performance.getEntriesByType('navigation');
+ const { pathname } = window.location;
+ if (!nav || new URL(nav.name).pathname === pathname) {
+ return;
+ }
+ const guardKey = `apisix-404-reload:${pathname}`;
+ if (window.sessionStorage.getItem(guardKey)) {
+ return;
+ }
+ window.sessionStorage.setItem(guardKey, '1');
+ window.location.reload();
+ } catch {
+ // performance / sessionStorage / URL unavailable — leave the 404 as-is.
+ }
+ }, []);
+
+ return (
+ <Layout
+ title={translate({
+ id: 'theme.NotFound.title',
+ message: 'Page Not Found',
+ })}
+ >
+ <Head>
+ <meta name="robots" content="noindex,follow" />
+ </Head>
+ <main className={style.container}>
+ <section>
+ <Fitty tagName="h1" contentEditable>404</Fitty>
+ <Fitty tagName="h2">
+ Page Not Found
+ </Fitty>
+ </section>
+ <p>
+ We could not find what you were looking for.
+ </p>
+ <p>
+ If you think this link should not be broken, please
+ {' '}
+ <Link
href="https://github.com/apache/apisix-website/issues/new/choose"
target="_blank" rel="noreferrer">submit an Issue</Link>
+ .
+ </p>
+ <p>
+ You can also open the
+ {' '}
+ <Link to="/">home page</Link>
+ ,
+ {' '}
+ <Link to="/docs/">documentation</Link>
+ ,
+ {' '}
+ <Link to="/blog/">blog</Link>
+ , or
+ {' '}
+ <a
+ role="button"
+ href="#"
+ onClick={() => {
+ window?.history.back();
+ }}
+ >
+ return to the source page
+ </a>
+ .
+ </p>
+ </main>
+ </Layout>
+ );
+};
export default NotFound;
diff --git a/website/src/theme/NotFound/index.tsx
b/website/src/theme/NotFound/index.tsx
index c8a513b32b1..6a43297540d 100644
--- a/website/src/theme/NotFound/index.tsx
+++ b/website/src/theme/NotFound/index.tsx
@@ -1,6 +1,6 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import type { FC } from 'react';
-import React from 'react';
+import React, { useEffect } from 'react';
import Layout from '@theme/Layout';
import Head from '@docusaurus/Head';
import { translate } from '@docusaurus/Translate';
@@ -8,57 +8,89 @@ import Link from '@docusaurus/Link';
import style from './styles.module.scss';
import Fitty from './Fitty';
-const NotFound: FC = () => (
- <Layout
- title={translate({
- id: 'theme.NotFound.title',
- message: 'Page Not Found',
- })}
- >
- <Head>
- <meta name="robots" content="noindex,follow" />
- </Head>
- <main className={style.container}>
- <section>
- <Fitty tagName="h1" contentEditable>404</Fitty>
- <Fitty tagName="h2">
- Page Not Found
- </Fitty>
- </section>
- <p>
- We could not find what you were looking for.
- </p>
- <p>
- If you think this link should not be broken, please
- {' '}
- <Link
href="https://github.com/apache/apisix-website/issues/new/choose"
target="_blank" rel="noreferrer">submit an Issue</Link>
- .
- </p>
- <p>
- You can also open the
- {' '}
- <Link to="/">home page</Link>
- ,
- {' '}
- <Link to="/docs/">documentation</Link>
- ,
- {' '}
- <Link to="/blog/">blog</Link>
- , or
- {' '}
- <a
- role="button"
- href="#"
- onClick={() => {
- window?.history.back();
- }}
- >
- return to the source page
- </a>
- .
- </p>
- </main>
- </Layout>
-);
+const NotFound: FC = () => {
+ useEffect(() => {
+ // The site is rebuilt and republished frequently, and each build replaces
the
+ // previous one's content-hashed chunks. A page still open from an older
build
+ // can client-navigate to a route that is missing from its stale route
manifest
+ // and land here, even though the server serves that page fine. Recover
with a
+ // single hard reload so the current build is fetched.
+ //
+ // Only do this when the 404 surfaced during a client-side navigation, not
on
+ // the document the server returned directly — that is a genuine 404 and is
+ // left untouched. A per-path guard reloads at most once as a safety
backstop.
+ try {
+ if (typeof window === 'undefined' || !window.performance) {
+ return;
+ }
+ const [nav] = window.performance.getEntriesByType('navigation');
+ const { pathname } = window.location;
+ if (!nav || new URL(nav.name).pathname === pathname) {
+ return;
+ }
+ const guardKey = `apisix-404-reload:${pathname}`;
+ if (window.sessionStorage.getItem(guardKey)) {
+ return;
+ }
+ window.sessionStorage.setItem(guardKey, '1');
+ window.location.reload();
+ } catch {
+ // performance / sessionStorage / URL unavailable — leave the 404 as-is.
+ }
+ }, []);
+
+ return (
+ <Layout
+ title={translate({
+ id: 'theme.NotFound.title',
+ message: 'Page Not Found',
+ })}
+ >
+ <Head>
+ <meta name="robots" content="noindex,follow" />
+ </Head>
+ <main className={style.container}>
+ <section>
+ <Fitty tagName="h1" contentEditable>404</Fitty>
+ <Fitty tagName="h2">
+ Page Not Found
+ </Fitty>
+ </section>
+ <p>
+ We could not find what you were looking for.
+ </p>
+ <p>
+ If you think this link should not be broken, please
+ {' '}
+ <Link
href="https://github.com/apache/apisix-website/issues/new/choose"
target="_blank" rel="noreferrer">submit an Issue</Link>
+ .
+ </p>
+ <p>
+ You can also open the
+ {' '}
+ <Link to="/">home page</Link>
+ ,
+ {' '}
+ <Link to="/docs/">documentation</Link>
+ ,
+ {' '}
+ <Link to="/blog/">blog</Link>
+ , or
+ {' '}
+ <a
+ role="button"
+ href="#"
+ onClick={() => {
+ window?.history.back();
+ }}
+ >
+ return to the source page
+ </a>
+ .
+ </p>
+ </main>
+ </Layout>
+ );
+};
export default NotFound;