Skip to content

Commit 19a3d32

Browse files
morningmanclaude
andauthored
[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) <noreply@anthropic.com>
1 parent a7cfd28 commit 19a3d32

5 files changed

Lines changed: 247 additions & 148 deletions

File tree

scripts/verify-htaccess.sh

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env bash
2+
# Local verification harness for static/.htaccess. Does NOT build the site —
3+
# only stages the dummy files needed to exercise the rewrite rules.
4+
#
5+
# Usage:
6+
# bash scripts/verify-htaccess.sh start # boot httpd in foreground (Ctrl-C to stop)
7+
# bash scripts/verify-htaccess.sh test # run curl assertions in another shell
8+
set -euo pipefail
9+
10+
ROOT="${TMPDIR:-/tmp}/doris-htaccess-test"
11+
PORT=8000
12+
HTACCESS_SRC="$(cd "$(dirname "$0")/.." && pwd)/static/.htaccess"
13+
14+
stage() {
15+
rm -rf "$ROOT"
16+
mkdir -p "$ROOT"/{docs/dev/install,docs-next/dev/getting-started/what-is-apache-doris,zh-CN}
17+
cp "$HTACCESS_SRC" "$ROOT/.htaccess"
18+
19+
printf 'GENERIC_404\n' > "$ROOT/404.html"
20+
printf 'ZH_404\n' > "$ROOT/zh-CN/404.html"
21+
# Simulate a file emitted by createRedirects — must short-circuit the rewrite.
22+
printf 'EXISTING_REDIRECT_FILE\n' > "$ROOT/docs/dev/install/index.html"
23+
# The eventual 301 target.
24+
printf 'NEW_DEV_LANDING\n' > "$ROOT/docs-next/dev/getting-started/what-is-apache-doris/index.html"
25+
26+
cat > "$ROOT/httpd.conf" <<EOF
27+
ServerName localhost
28+
Listen $PORT
29+
LoadModule mpm_event_module /usr/libexec/apache2/mod_mpm_event.so
30+
LoadModule unixd_module /usr/libexec/apache2/mod_unixd.so
31+
LoadModule authz_core_module /usr/libexec/apache2/mod_authz_core.so
32+
LoadModule rewrite_module /usr/libexec/apache2/mod_rewrite.so
33+
LoadModule headers_module /usr/libexec/apache2/mod_headers.so
34+
LoadModule dir_module /usr/libexec/apache2/mod_dir.so
35+
LoadModule mime_module /usr/libexec/apache2/mod_mime.so
36+
LoadModule log_config_module /usr/libexec/apache2/mod_log_config.so
37+
38+
TypesConfig /private/etc/apache2/mime.types
39+
DirectoryIndex index.html
40+
41+
ErrorLog $ROOT/error.log
42+
PidFile $ROOT/httpd.pid
43+
LogLevel warn rewrite:trace3
44+
45+
DocumentRoot "$ROOT"
46+
<Directory "$ROOT">
47+
AllowOverride All
48+
Require all granted
49+
</Directory>
50+
EOF
51+
}
52+
53+
case "${1:-}" in
54+
start)
55+
stage
56+
echo "[stage] root: $ROOT"
57+
echo "[stage] running httpd on http://localhost:$PORT (Ctrl-C to stop)"
58+
echo "[stage] rewrite trace: tail -f $ROOT/error.log"
59+
exec /usr/sbin/httpd -f "$ROOT/httpd.conf" -X
60+
;;
61+
test)
62+
BASE="http://localhost:$PORT"
63+
run() {
64+
local desc="$1" url="$2" want_code="$3" want_body_or_loc="$4"
65+
local out code loc body
66+
out="$(curl -sS -o "$ROOT/last-body" -w '%{http_code} %{redirect_url}' "$BASE$url")"
67+
code="${out%% *}"
68+
loc="${out#* }"
69+
body="$(tr -d '\r\n' < "$ROOT/last-body")"
70+
local got
71+
if [[ "$want_code" == 301 ]]; then got="$loc"; else got="$body"; fi
72+
if [[ "$code" == "$want_code" && "$got" == *"$want_body_or_loc"* ]]; then
73+
echo "PASS $desc -> $code"
74+
else
75+
echo "FAIL $desc url=$url expected=$want_code/$want_body_or_loc got=$code/$got"
76+
exit 1
77+
fi
78+
}
79+
run 'legacy /docs/dev/<missing> -> 301 new landing' '/docs/dev/gettingStarted/intro' 301 '/docs-next/dev/getting-started/what-is-apache-doris'
80+
run 'legacy /docs/dev/install/ -> 200 existing redirect file' '/docs/dev/install/' 200 'EXISTING_REDIRECT_FILE'
81+
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'
82+
run 'random EN 404 -> /404.html' '/totally/missing/path' 404 'GENERIC_404'
83+
run 'random zh-CN 404 -> /zh-CN/404.html' '/zh-CN/totally/missing/path' 404 'ZH_404'
84+
run '/docs/devops bystander -> not rewritten (404)' '/docs/devops' 404 'GENERIC_404'
85+
echo 'all assertions passed'
86+
;;
87+
*)
88+
echo "usage: bash $0 start|test"
89+
exit 2
90+
;;
91+
esac

src/theme/NotFound.js

Lines changed: 0 additions & 142 deletions
This file was deleted.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import React, { useEffect, useState } from 'react';
2+
import Translate from '@docusaurus/Translate';
3+
import ExternalLink from '@site/src/components/external-link/external-link';
4+
import { ExternalLinkArrowIcon } from '@site/src/components/Icons/external-link-arrow-icon';
5+
6+
// Dev doc paths land here in two flavors:
7+
// - legacy /docs/dev/* (and zh-CN counterpart) when no 1:1 redirect target
8+
// was emitted by createRedirects;
9+
// - new /docs-next/dev/* when DocRoot can't resolve the slug and falls back
10+
// to NotFoundContent (bypassing the outer @theme/NotFound).
11+
// Both should land on a guidance card pointing at the new Dev docs entry.
12+
const DEV_GUIDANCE = {
13+
en: {
14+
title: 'This Dev doc has moved',
15+
description:
16+
'The page you requested is not available at this URL. The Dev docs now live under /docs-next/dev/.',
17+
linkLabel: 'Go to new Dev docs',
18+
linkTo: '/docs-next/dev/getting-started/what-is-apache-doris',
19+
},
20+
'zh-CN': {
21+
title: 'Dev 文档已迁移',
22+
description: '此 URL 对应的页面不再可用。新版 Dev 文档位于 /docs-next/dev/ 下。',
23+
linkLabel: '前往新版 Dev 文档',
24+
linkTo: '/zh-CN/docs-next/dev/getting-started/what-is-apache-doris',
25+
},
26+
};
27+
28+
function detectDevDocLocale(pathname) {
29+
if (!pathname) return null;
30+
if (/^\/zh-CN\/docs(-next)?\/dev(\/|$)/.test(pathname)) return 'zh-CN';
31+
if (/^\/docs(-next)?\/dev(\/|$)/.test(pathname)) return 'en';
32+
return null;
33+
}
34+
35+
function DevDocGuidance({ locale }) {
36+
const copy = DEV_GUIDANCE[locale];
37+
return (
38+
<>
39+
<h1 className="text-[1.75rem] text-[#1D1D1D] leading-[1.6] text-center">
40+
{copy.title}
41+
</h1>
42+
<p className="text-center mt-2 text-sm text-[#8592A6]">{copy.description}</p>
43+
<div className="flex justify-center gap-x-6 lg:gap-x-10 mt-10">
44+
<div className="w-[12.5rem]">
45+
<ExternalLink
46+
to={copy.linkTo}
47+
label={copy.linkLabel}
48+
className="text-sm h-[2.625rem] bg-primary text-white rounded-md hover:text-white cursor-pointer"
49+
linkIcon={<ExternalLinkArrowIcon />}
50+
/>
51+
</div>
52+
</div>
53+
</>
54+
);
55+
}
56+
57+
function GenericNotFound() {
58+
return (
59+
<>
60+
<h1 className="text-[1.75rem] text-[#1D1D1D] leading-[1.6] text-center">
61+
<Translate id="theme.NotFound.title" description="The title of the 404 page">
62+
Page Not Found
63+
</Translate>
64+
</h1>
65+
<p className="text-center mt-2 text-sm text-[#8592A6]">
66+
<Translate id="theme.NotFound.p1" description="The first paragraph of the 404 page">
67+
Oops! The page you are looking for can't be found. In any case, try to look for a
68+
different page or report this issue.
69+
</Translate>
70+
</p>
71+
<div className="flex justify-center gap-x-6 lg:gap-x-10 mt-10">
72+
<div className="w-[9.75rem]">
73+
<ExternalLink
74+
to="/"
75+
label="Go to home"
76+
className="text-sm h-[2.625rem] bg-primary text-white rounded-md hover:text-white cursor-pointer"
77+
linkIcon={<ExternalLinkArrowIcon />}
78+
/>
79+
</div>
80+
<div className="w-[9.75rem]">
81+
<ExternalLink
82+
label="Report this issue"
83+
linkIcon={<ExternalLinkArrowIcon />}
84+
to="https://github.com/apache/doris-website/issues"
85+
className="text-sm border border-primary h-[2.625rem] rounded-md text-primary cursor-pointer"
86+
/>
87+
</div>
88+
</div>
89+
</>
90+
);
91+
}
92+
93+
export default function NotFoundContent({ className }) {
94+
const [devLocale, setDevLocale] = useState(null);
95+
96+
useEffect(() => {
97+
if (typeof window !== 'undefined') {
98+
setDevLocale(detectDevDocLocale(window.location.pathname));
99+
}
100+
}, []);
101+
102+
return (
103+
<main className={`container margin-vert--xl ${className || ''}`}>
104+
<div className="row">
105+
<div className="col">
106+
<div className="flex justify-center mb-10">
107+
<img
108+
style={{ width: 120 }}
109+
src={require('@site/static/images/empty-data.png').default}
110+
alt=""
111+
/>
112+
</div>
113+
{devLocale ? <DevDocGuidance locale={devLocale} /> : <GenericNotFound />}
114+
</div>
115+
</div>
116+
</main>
117+
);
118+
}

src/theme/NotFound/index.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import { translate } from '@docusaurus/Translate';
3+
import { PageMetadata } from '@docusaurus/theme-common';
4+
import Layout from '@theme/Layout';
5+
import NotFoundContent from '@theme/NotFound/Content';
6+
7+
export default function NotFound() {
8+
return (
9+
<>
10+
<PageMetadata
11+
title={translate({
12+
id: 'theme.NotFound.title',
13+
message: 'Page Not Found',
14+
})}
15+
/>
16+
<Layout>
17+
<NotFoundContent />
18+
</Layout>
19+
</>
20+
);
21+
}

0 commit comments

Comments
 (0)