This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-website.git
The following commit(s) were added to refs/heads/master by this push:
new 19a3d329c73 [fix] swizzle NotFound/Content and 301 unmatched
/docs/dev/* in .htaccess (#3621)
19a3d329c73 is described below
commit 19a3d329c73d303d61bc7e7b515970aef6e3b680
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Wed May 6 17:19:53 2026 -0700
[fix] swizzle NotFound/Content and 301 unmatched /docs/dev/* in .htaccess
(#3621)
The previous fix only swizzled the outer @theme/NotFound, so DocRoot's
fallback (@theme/NotFound/Content) still rendered the default Docusaurus
copy for missing docs under /docs-next/dev/. And legacy /docs/dev/* with
no 1:1 redirect target hit a SPA hydration mismatch and rendered blank.
Now the inner Content is swizzled with the same dev-doc detection, and
unmatched /docs/dev/* (en + zh-CN) is 301'd at the Apache layer before
React boots.
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
scripts/verify-htaccess.sh | 91 +++++++++++++++++++++++
src/theme/NotFound.js | 142 ------------------------------------
src/theme/NotFound/Content/index.js | 118 ++++++++++++++++++++++++++++++
src/theme/NotFound/index.js | 21 ++++++
static/.htaccess | 23 ++++--
5 files changed, 247 insertions(+), 148 deletions(-)
diff --git a/scripts/verify-htaccess.sh b/scripts/verify-htaccess.sh
new file mode 100755
index 00000000000..75ec7d77843
--- /dev/null
+++ b/scripts/verify-htaccess.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+# Local verification harness for static/.htaccess. Does NOT build the site —
+# only stages the dummy files needed to exercise the rewrite rules.
+#
+# Usage:
+# bash scripts/verify-htaccess.sh start # boot httpd in foreground
(Ctrl-C to stop)
+# bash scripts/verify-htaccess.sh test # run curl assertions in another
shell
+set -euo pipefail
+
+ROOT="${TMPDIR:-/tmp}/doris-htaccess-test"
+PORT=8000
+HTACCESS_SRC="$(cd "$(dirname "$0")/.." && pwd)/static/.htaccess"
+
+stage() {
+ rm -rf "$ROOT"
+ mkdir -p
"$ROOT"/{docs/dev/install,docs-next/dev/getting-started/what-is-apache-doris,zh-CN}
+ cp "$HTACCESS_SRC" "$ROOT/.htaccess"
+
+ printf 'GENERIC_404\n' > "$ROOT/404.html"
+ printf 'ZH_404\n' > "$ROOT/zh-CN/404.html"
+ # Simulate a file emitted by createRedirects — must short-circuit the
rewrite.
+ printf 'EXISTING_REDIRECT_FILE\n' > "$ROOT/docs/dev/install/index.html"
+ # The eventual 301 target.
+ printf 'NEW_DEV_LANDING\n' >
"$ROOT/docs-next/dev/getting-started/what-is-apache-doris/index.html"
+
+ cat > "$ROOT/httpd.conf" <<EOF
+ServerName localhost
+Listen $PORT
+LoadModule mpm_event_module /usr/libexec/apache2/mod_mpm_event.so
+LoadModule unixd_module /usr/libexec/apache2/mod_unixd.so
+LoadModule authz_core_module /usr/libexec/apache2/mod_authz_core.so
+LoadModule rewrite_module /usr/libexec/apache2/mod_rewrite.so
+LoadModule headers_module /usr/libexec/apache2/mod_headers.so
+LoadModule dir_module /usr/libexec/apache2/mod_dir.so
+LoadModule mime_module /usr/libexec/apache2/mod_mime.so
+LoadModule log_config_module /usr/libexec/apache2/mod_log_config.so
+
+TypesConfig /private/etc/apache2/mime.types
+DirectoryIndex index.html
+
+ErrorLog $ROOT/error.log
+PidFile $ROOT/httpd.pid
+LogLevel warn rewrite:trace3
+
+DocumentRoot "$ROOT"
+<Directory "$ROOT">
+ AllowOverride All
+ Require all granted
+</Directory>
+EOF
+}
+
+case "${1:-}" in
+ start)
+ stage
+ echo "[stage] root: $ROOT"
+ echo "[stage] running httpd on http://localhost:$PORT (Ctrl-C to stop)"
+ echo "[stage] rewrite trace: tail -f $ROOT/error.log"
+ exec /usr/sbin/httpd -f "$ROOT/httpd.conf" -X
+ ;;
+ test)
+ BASE="http://localhost:$PORT"
+ run() {
+ local desc="$1" url="$2" want_code="$3" want_body_or_loc="$4"
+ local out code loc body
+ out="$(curl -sS -o "$ROOT/last-body" -w '%{http_code}
%{redirect_url}' "$BASE$url")"
+ code="${out%% *}"
+ loc="${out#* }"
+ body="$(tr -d '\r\n' < "$ROOT/last-body")"
+ local got
+ if [[ "$want_code" == 301 ]]; then got="$loc"; else got="$body"; fi
+ if [[ "$code" == "$want_code" && "$got" == *"$want_body_or_loc"*
]]; then
+ echo "PASS $desc -> $code"
+ else
+ echo "FAIL $desc url=$url
expected=$want_code/$want_body_or_loc got=$code/$got"
+ exit 1
+ fi
+ }
+ run 'legacy /docs/dev/<missing> -> 301 new landing'
'/docs/dev/gettingStarted/intro' 301
'/docs-next/dev/getting-started/what-is-apache-doris'
+ run 'legacy /docs/dev/install/ -> 200 existing redirect file'
'/docs/dev/install/' 200 'EXISTING_REDIRECT_FILE'
+ run 'zh-CN /docs/dev/<missing> -> 301 zh-CN new landing'
'/zh-CN/docs/dev/whatever' 301
'/zh-CN/docs-next/dev/getting-started/what-is-apache-doris'
+ run 'random EN 404 -> /404.html'
'/totally/missing/path' 404 'GENERIC_404'
+ run 'random zh-CN 404 -> /zh-CN/404.html'
'/zh-CN/totally/missing/path' 404 'ZH_404'
+ run '/docs/devops bystander -> not rewritten (404)'
'/docs/devops' 404 'GENERIC_404'
+ echo 'all assertions passed'
+ ;;
+ *)
+ echo "usage: bash $0 start|test"
+ exit 2
+ ;;
+esac
diff --git a/src/theme/NotFound.js b/src/theme/NotFound.js
deleted file mode 100644
index 79d1f819b79..00000000000
--- a/src/theme/NotFound.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import Translate, { translate } from '@docusaurus/Translate';
-import { PageMetadata } from '@docusaurus/theme-common';
-import Layout from '@theme/Layout';
-import ExternalLink from '../components/external-link/external-link';
-import { ExternalLinkArrowIcon } from
'@site/src/components/Icons/external-link-arrow-icon';
-
-// Legacy /docs/dev/* URLs may still be linked from external sites. Pages with
a
-// 1:1 match under /docs-next/dev/ are redirected at build time; pages without
-// one land here. Detect the legacy prefix and show guidance to the new Dev
docs
-// entry instead of the generic 404.
-const LEGACY_DEV_GUIDANCE = {
- en: {
- title: 'This Dev doc has moved',
- description:
- 'The legacy /docs/dev/ tree has been retired. The page you
requested is no longer available at this URL.',
- linkLabel: 'Go to new Dev docs',
- linkTo: '/docs-next/dev/getting-started/what-is-apache-doris',
- },
- 'zh-CN': {
- title: 'Dev 文档已迁移',
- description:
- '/docs/dev/ 下的旧文档已下线,此 URL 对应的页面不再可用。请前往新版 Dev 文档继续浏览。',
- linkLabel: '前往新版 Dev 文档',
- linkTo: '/zh-CN/docs-next/dev/getting-started/what-is-apache-doris',
- },
-};
-
-function detectLegacyDevLocale(pathname) {
- if (!pathname) return null;
- if (pathname === '/zh-CN/docs/dev' ||
pathname.startsWith('/zh-CN/docs/dev/')) {
- return 'zh-CN';
- }
- if (pathname === '/docs/dev' || pathname.startsWith('/docs/dev/')) {
- return 'en';
- }
- return null;
-}
-
-function LegacyDevGuidance({ locale }) {
- const copy = LEGACY_DEV_GUIDANCE[locale];
- return (
- <main className="container margin-vert--xl">
- <div className="row">
- <div className="col">
- <div className="flex justify-center mb-10">
- <img
- style={{ width: 120 }}
-
src={require('@site/static/images/empty-data.png').default}
- alt=""
- />
- </div>
- <h1 className="text-[1.75rem] text-[#1D1D1D] leading-[1.6]
text-center">
- {copy.title}
- </h1>
- <p className="text-center mt-2 text-sm text-[#8592A6]">
- {copy.description}
- </p>
- <div className="flex justify-center gap-x-6 lg:gap-x-10
mt-10">
- <div className="w-[12.5rem]">
- <ExternalLink
- to={copy.linkTo}
- label={copy.linkLabel}
- className="text-sm h-[2.625rem] bg-primary
text-white rounded-md hover:text-white cursor-pointer"
- linkIcon={<ExternalLinkArrowIcon />}
- />
- </div>
- </div>
- </div>
- </div>
- </main>
- );
-}
-
-export default function NotFound() {
- const [legacyDevLocale, setLegacyDevLocale] = useState(null);
-
- useEffect(() => {
- if (typeof window !== 'undefined') {
-
setLegacyDevLocale(detectLegacyDevLocale(window.location.pathname));
- }
- }, []);
-
- return (
- <>
- <PageMetadata
- title={translate({
- id: 'theme.NotFound.title',
- message: 'Page Not Found',
- })}
- />
- <Layout>
- {legacyDevLocale ? (
- <LegacyDevGuidance locale={legacyDevLocale} />
- ) : (
- <main className="container margin-vert--xl">
- <div className="row">
- <div className="col">
- <div className="flex justify-center mb-10">
- <img
- style={{ width: 120 }}
-
src={require('@site/static/images/empty-data.png').default}
- alt=""
- />
- </div>
- <h1 className="text-[1.75rem] text-[#1D1D1D]
leading-[1.6] text-center">
- <Translate id="theme.NotFound.title"
description="The title of the 404 page">
- Page Not Found
- </Translate>
- </h1>
- <p className="text-center mt-2 text-sm
text-[#8592A6]">
- <Translate id="theme.NotFound.p1"
description="The first paragraph of the 404 page">
- Oops! The page you are looking for
can't be found. In any case, try to look for a
- different page or report this issue.
- </Translate>
- </p>
- <div className="flex justify-center gap-x-6
lg:gap-x-10 mt-10">
- <div className="w-[9.75rem]">
- <ExternalLink
- to="/"
- label="Go to home"
- className="text-sm h-[2.625rem]
bg-primary text-white rounded-md hover:text-white cursor-pointer"
- linkIcon={<ExternalLinkArrowIcon
/>}
- />
- </div>
- <div className="w-[9.75rem]">
- <ExternalLink
- label="Report this issue"
- linkIcon={<ExternalLinkArrowIcon
/>}
-
to="https://github.com/apache/doris-website/issues"
- className="text-sm border
border-primary h-[2.625rem] rounded-md text-primary cursor-pointer"
- />
- </div>
- </div>
- </div>
- </div>
- </main>
- )}
- </Layout>
- </>
- );
-}
diff --git a/src/theme/NotFound/Content/index.js
b/src/theme/NotFound/Content/index.js
new file mode 100644
index 00000000000..bcda25a7a71
--- /dev/null
+++ b/src/theme/NotFound/Content/index.js
@@ -0,0 +1,118 @@
+import React, { useEffect, useState } from 'react';
+import Translate from '@docusaurus/Translate';
+import ExternalLink from '@site/src/components/external-link/external-link';
+import { ExternalLinkArrowIcon } from
'@site/src/components/Icons/external-link-arrow-icon';
+
+// Dev doc paths land here in two flavors:
+// - legacy /docs/dev/* (and zh-CN counterpart) when no 1:1 redirect target
+// was emitted by createRedirects;
+// - new /docs-next/dev/* when DocRoot can't resolve the slug and falls back
+// to NotFoundContent (bypassing the outer @theme/NotFound).
+// Both should land on a guidance card pointing at the new Dev docs entry.
+const DEV_GUIDANCE = {
+ en: {
+ title: 'This Dev doc has moved',
+ description:
+ 'The page you requested is not available at this URL. The Dev docs
now live under /docs-next/dev/.',
+ linkLabel: 'Go to new Dev docs',
+ linkTo: '/docs-next/dev/getting-started/what-is-apache-doris',
+ },
+ 'zh-CN': {
+ title: 'Dev 文档已迁移',
+ description: '此 URL 对应的页面不再可用。新版 Dev 文档位于 /docs-next/dev/ 下。',
+ linkLabel: '前往新版 Dev 文档',
+ linkTo: '/zh-CN/docs-next/dev/getting-started/what-is-apache-doris',
+ },
+};
+
+function detectDevDocLocale(pathname) {
+ if (!pathname) return null;
+ if (/^\/zh-CN\/docs(-next)?\/dev(\/|$)/.test(pathname)) return 'zh-CN';
+ if (/^\/docs(-next)?\/dev(\/|$)/.test(pathname)) return 'en';
+ return null;
+}
+
+function DevDocGuidance({ locale }) {
+ const copy = DEV_GUIDANCE[locale];
+ return (
+ <>
+ <h1 className="text-[1.75rem] text-[#1D1D1D] leading-[1.6]
text-center">
+ {copy.title}
+ </h1>
+ <p className="text-center mt-2 text-sm
text-[#8592A6]">{copy.description}</p>
+ <div className="flex justify-center gap-x-6 lg:gap-x-10 mt-10">
+ <div className="w-[12.5rem]">
+ <ExternalLink
+ to={copy.linkTo}
+ label={copy.linkLabel}
+ className="text-sm h-[2.625rem] bg-primary text-white
rounded-md hover:text-white cursor-pointer"
+ linkIcon={<ExternalLinkArrowIcon />}
+ />
+ </div>
+ </div>
+ </>
+ );
+}
+
+function GenericNotFound() {
+ return (
+ <>
+ <h1 className="text-[1.75rem] text-[#1D1D1D] leading-[1.6]
text-center">
+ <Translate id="theme.NotFound.title" description="The title of
the 404 page">
+ Page Not Found
+ </Translate>
+ </h1>
+ <p className="text-center mt-2 text-sm text-[#8592A6]">
+ <Translate id="theme.NotFound.p1" description="The first
paragraph of the 404 page">
+ Oops! The page you are looking for can't be found. In any
case, try to look for a
+ different page or report this issue.
+ </Translate>
+ </p>
+ <div className="flex justify-center gap-x-6 lg:gap-x-10 mt-10">
+ <div className="w-[9.75rem]">
+ <ExternalLink
+ to="/"
+ label="Go to home"
+ className="text-sm h-[2.625rem] bg-primary text-white
rounded-md hover:text-white cursor-pointer"
+ linkIcon={<ExternalLinkArrowIcon />}
+ />
+ </div>
+ <div className="w-[9.75rem]">
+ <ExternalLink
+ label="Report this issue"
+ linkIcon={<ExternalLinkArrowIcon />}
+ to="https://github.com/apache/doris-website/issues"
+ className="text-sm border border-primary h-[2.625rem]
rounded-md text-primary cursor-pointer"
+ />
+ </div>
+ </div>
+ </>
+ );
+}
+
+export default function NotFoundContent({ className }) {
+ const [devLocale, setDevLocale] = useState(null);
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ setDevLocale(detectDevDocLocale(window.location.pathname));
+ }
+ }, []);
+
+ return (
+ <main className={`container margin-vert--xl ${className || ''}`}>
+ <div className="row">
+ <div className="col">
+ <div className="flex justify-center mb-10">
+ <img
+ style={{ width: 120 }}
+
src={require('@site/static/images/empty-data.png').default}
+ alt=""
+ />
+ </div>
+ {devLocale ? <DevDocGuidance locale={devLocale} /> :
<GenericNotFound />}
+ </div>
+ </div>
+ </main>
+ );
+}
diff --git a/src/theme/NotFound/index.js b/src/theme/NotFound/index.js
new file mode 100644
index 00000000000..5e16b00d757
--- /dev/null
+++ b/src/theme/NotFound/index.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { translate } from '@docusaurus/Translate';
+import { PageMetadata } from '@docusaurus/theme-common';
+import Layout from '@theme/Layout';
+import NotFoundContent from '@theme/NotFound/Content';
+
+export default function NotFound() {
+ return (
+ <>
+ <PageMetadata
+ title={translate({
+ id: 'theme.NotFound.title',
+ message: 'Page Not Found',
+ })}
+ />
+ <Layout>
+ <NotFoundContent />
+ </Layout>
+ </>
+ );
+}
diff --git a/static/.htaccess b/static/.htaccess
index 2b98f7179eb..6ab39df5dbd 100644
--- a/static/.htaccess
+++ b/static/.htaccess
@@ -2,14 +2,25 @@
Header set Content-Security-Policy "script-src 'self'
https://cdnd.selectdb.com widget.kapa.ai www.google.com https://hcaptcha.com
https://*.hcaptcha.com https://www.gstatic.com 'unsafe-inline' 'unsafe-eval';
connect-src 'self' proxy.kapa.ai kapa-widget-proxy-la7dkmplpq-uc.a.run.app
metrics.kapa.ai https://hcaptcha.com https://*.hcaptcha.com www.google.com;
frame-src 'self' www.google.com https://hcaptcha.com https://*.hcaptcha.com;
worker-src 'self' https://cdnd.selectdb.com blob:; [...]
</IfModule>
-# Serve Docusaurus's built 404 page (which mounts src/theme/NotFound.js) for
any
-# unknown URL, instead of httpd's default ErrorDocument. The browser keeps the
-# original path, so NotFound.js can detect legacy /docs/dev/* and show
guidance.
-# For /zh-CN/* misses, rewrite to the zh-CN-locale 404 so the surrounding
Layout
-# (header/footer/i18n context) renders in Chinese; the URL is preserved so the
-# React component still reads the original pathname for legacy detection.
+# Legacy /docs/dev/* paths whose 1:1 target was retired (renamed slugs, removed
+# pages) — server-side 301 to the new Dev docs landing. Doing it here avoids
+# the SPA hydration mismatch that results from /404.html being served on a
+# path the React router still treats as inside the docs prefix.
+# Paths emitted by createRedirects (1:1 matches) live on disk and short-circuit
+# via the -f check, so this only fires for genuinely missing slugs.
<IfModule mod_rewrite.c>
RewriteEngine On
+
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^docs/dev(/.*)?$
/docs-next/dev/getting-started/what-is-apache-doris [R=301,L]
+
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^zh-CN/docs/dev(/.*)?$
/zh-CN/docs-next/dev/getting-started/what-is-apache-doris [R=301,L]
+
+ # Other zh-CN misses — render the zh-CN-locale 404 so the chrome/i18n
+ # context (header/footer) is Chinese. URL is preserved.
RewriteCond %{REQUEST_URI} ^/zh-CN/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]