This is an automated email from the ASF dual-hosted git repository.
PDavid pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/master by this push:
new fc5b991f733 HBASE-30127 Improve Website's SEO (#8157)
fc5b991f733 is described below
commit fc5b991f733b62a33508cce86edb0e9dcf1dae4f
Author: Yurii Palamarchuk <[email protected]>
AuthorDate: Thu Jun 4 16:20:05 2026 +0200
HBASE-30127 Improve Website's SEO (#8157)
This change improves SEO (Search Engine Optimization) by adding proper
cache headers, good looking 404 page, and sitemap.
Signed-off-by: Dávid Paksy <[email protected]>
---
hbase-website/.gitignore | 1 +
hbase-website/README.md | 15 +-
.../_mdx/(multi-page)/upgrading/version-number.mdx | 13 +-
hbase-website/app/root.tsx | 47 +++-
hbase-website/package-lock.json | 166 +++++++++++++-
hbase-website/package.json | 6 +-
hbase-website/public/404.html | 245 +++++++++++++++++++++
hbase-website/public/robots.txt | 5 +
hbase-website/scripts/generate-sitemap.ts | 116 ++++++++++
hbase-website/unit-tests/generate-sitemap.test.ts | 90 ++++++++
pom.xml | 2 +
11 files changed, 677 insertions(+), 29 deletions(-)
diff --git a/hbase-website/.gitignore b/hbase-website/.gitignore
index 00909066a6f..76974dfd287 100644
--- a/hbase-website/.gitignore
+++ b/hbase-website/.gitignore
@@ -39,6 +39,7 @@ lerna-debug.log*
/app/pages/_docs/docs/_mdx/(multi-page)/configuration/hbase-default.md
/app/lib/export-pdf/hbase-version.json
/public/books/**
+/public/sitemap.xml
# Playwright
node_modules/
diff --git a/hbase-website/README.md b/hbase-website/README.md
index 0685e87f637..cbc365b8966 100644
--- a/hbase-website/README.md
+++ b/hbase-website/README.md
@@ -476,16 +476,18 @@ When you run `mvn site`, the website module automatically:
- `npm run extract-hbase-config` - Extract data from `hbase-default.xml` to
`app/pages/_docs/docs/_mdx/(multi-page)/configuration/hbase-default.md`
- `npm run extract-hbase-version` - Extract version from root `pom.xml` to
`app/lib/export-pdf/hbase-version.json`
- `npm run test:unit:run` - Vitest unit tests
- - `npm run test:e2e` - Playwright e2e tests
- `npm run build` - Production build
+ - `npm run generate-sitemap` - Generates `public/sitemap.xml` and
`build/client/sitemap.xml`
+ - `npm run test:e2e` - Playwright e2e tests
`npm run ci-skip-tests` executes:
- `npm run extract-developers` - Extract developers from parent pom.xml
- `npm run extract-hbase-config` - Extract data from `hbase-default.xml` to
`app/pages/_docs/docs/_mdx/(multi-page)/configuration/hbase-default.md`
- `npm run extract-hbase-version` - Extract version from root `pom.xml` to
`app/lib/export-pdf/hbase-version.json`
+ - `npm run build` - Production build
+ - `npm run generate-sitemap` - Generates `public/sitemap.xml` and
`build/client/sitemap.xml`
- `npx playwright install` - Installs Playwright browsers
- `npm run export-pdf` - Generates docs PDF assets through Playwright
- - `npm run build` - Production build
6. **Build Output**: Generated files are in `build/` directory
@@ -535,6 +537,15 @@ mvn clean install -DskipSite
### Deployment
+#### Update the 404 Page
+
+- Edit the standalone static page in `public/404.html`.
+- The static 404 page supports dark mode without React: it applies the saved
+ `localStorage.theme` value when present and otherwise falls back to
+ `prefers-color-scheme`.
+- `public/robots.txt` excludes `/404.html` from crawlers.
+- `scripts/generate-sitemap.ts` excludes `404.html` from generated sitemaps.
+
#### Static Hosting
Since this site uses Static Site Generation (SSG), you can deploy the
`build/client/` directory to any static file host:
diff --git
a/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/upgrading/version-number.mdx
b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/upgrading/version-number.mdx
index 000143c2004..4735581c7d3 100644
---
a/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/upgrading/version-number.mdx
+++
b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/upgrading/version-number.mdx
@@ -88,13 +88,18 @@ In addition to the usual API versioning considerations
HBase has other compatibi
- A minor upgrade requires no application/client code modification. Ideally it
would be a drop-in replacement but client code, coprocessors, filters, etc
might have to be recompiled if new jars are used.
- A major upgrade allows the HBase community to make breaking changes.
-#### Compatibility Matrix: [^3] [!toc]
+#### Compatibility Matrix: [!toc]
+
+<Callout type="warn">
+ *Please note, this indicates what could break, not that it will break. We
will/should add
+ specifics in our release notes.*
+</Callout>
| | Major | Minor | Patch |
| ----------------------------------------- | :----: | :---: | :---: |
| Client-Server wire Compatibility | N | Y | Y |
| Server-Server Compatibility | N | Y | Y |
-| File Format Compatibility | N [^4] | Y | Y |
+| File Format Compatibility | N [^3] | Y | Y |
| Client API Compatibility | N | Y | Y |
| Client Binary Compatibility | N | N | Y |
| **Server-Side Limited API Compatibility** | | | |
@@ -149,6 +154,4 @@ When we say two HBase versions are compatible, we mean that
the versions are wir
[^2]: See http://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html.
-[^3]: Note that this indicates what could break, not that it will break. We
will/should add specifics in our release notes.
-
-[^4]: Running an offline upgrade tool without downgrade might be needed. We
will typically only support migrating data from major version X to major
version X+1.
+[^3]: Running an offline upgrade tool without downgrade might be needed. We
will typically only support migrating data from major version X to major
version X+1.
diff --git a/hbase-website/app/root.tsx b/hbase-website/app/root.tsx
index dda5666f9b2..e80df149884 100644
--- a/hbase-website/app/root.tsx
+++ b/hbase-website/app/root.tsx
@@ -29,6 +29,7 @@ import type { Route } from "./+types/root";
import appStyles from "./app.css?url";
import "katex/dist/katex.css";
import { ThemeProvider } from "./lib/theme-provider";
+import { Button } from "./ui/button";
export const links: Route.LinksFunction = () => [
{
@@ -96,12 +97,14 @@ export default function App() {
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- let message = "Oops!";
+ let eyebrow = "Error";
+ let message = "Something went wrong";
let details = "An unexpected error occurred.";
let stack: string | undefined;
if (isRouteErrorResponse(error)) {
- message = error.status === 404 ? "404" : "Error";
+ eyebrow = String(error.status);
+ message = error.status === 404 ? "Page not found" : "Request failed";
details =
error.status === 404 ? "The requested page could not be found." :
error.statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) {
@@ -110,14 +113,38 @@ export function ErrorBoundary({ error }:
Route.ErrorBoundaryProps) {
}
return (
- <main className="container mx-auto p-4 pt-16">
- <h1>{message}</h1>
- <p>{details}</p>
- {stack && (
- <pre className="w-full overflow-x-auto p-4">
- <code>{stack}</code>
- </pre>
- )}
+ <main className="grid min-h-screen place-items-center
bg-[radial-gradient(circle_at_top,rgba(186,22,12,0.08),transparent_32rem)] px-4
py-16">
+ <section
+ className="mx-auto flex w-full max-w-2xl flex-col items-center
text-center"
+ aria-labelledby="error-title"
+ >
+ <img className="mb-8 h-auto w-36" src="/images/logo.svg" alt="Apache
HBase" />
+ <p className="text-muted-foreground text-sm font-semibold
tracking-[0.3em] uppercase">
+ {eyebrow}
+ </p>
+ <h1
+ id="error-title"
+ className="mt-4 text-4xl font-semibold tracking-tight text-balance
md:text-6xl"
+ >
+ {message}
+ </h1>
+ <p className="text-muted-foreground mt-5 max-w-xl text-lg leading-8
text-pretty md:text-xl">
+ {details}
+ </p>
+ <div className="mt-8 flex flex-wrap items-center justify-center gap-3">
+ <Button asChild size="lg">
+ <a href="/">Go back home</a>
+ </Button>
+ <Button asChild variant="outline" size="lg">
+ <a href="/docs/">Read documentation</a>
+ </Button>
+ </div>
+ {stack && (
+ <pre className="bg-muted/50 text-muted-foreground border-border mt-8
max-h-80 w-full overflow-x-auto rounded-lg border p-4 text-left text-sm">
+ <code>{stack}</code>
+ </pre>
+ )}
+ </section>
</main>
);
}
diff --git a/hbase-website/package-lock.json b/hbase-website/package-lock.json
index 59ad6848799..9b8eaef3122 100644
--- a/hbase-website/package-lock.json
+++ b/hbase-website/package-lock.json
@@ -22,7 +22,7 @@
"@radix-ui/react-presence": "^1.1.5",
"@radix-ui/react-scroll-area": "1.2.2",
"@radix-ui/react-separator": "1.1.1",
- "@radix-ui/react-slot": "*",
+ "@radix-ui/react-slot": "latest",
"@radix-ui/react-tabs": "1.1.2",
"@radix-ui/react-tooltip": "1.1.6",
"@react-router/node": "^7.12.0",
@@ -84,6 +84,7 @@
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"serve": "^14.2.6",
+ "sitemap": "^9.0.1",
"tailwindcss": "^4.1.13",
"tsx": "^4.21.0",
"tw-animate-css": "1.3.3",
@@ -3573,6 +3574,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3586,6 +3588,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3599,6 +3602,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3612,6 +3616,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3625,6 +3630,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3638,6 +3644,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3651,6 +3658,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3664,6 +3672,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3677,6 +3686,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3690,6 +3700,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3703,6 +3714,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3716,6 +3728,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3729,6 +3742,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3742,6 +3756,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3755,6 +3770,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3768,6 +3784,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3781,6 +3798,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3794,6 +3812,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3807,6 +3826,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3820,6 +3840,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3833,6 +3854,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3846,6 +3868,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3859,6 +3882,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3872,6 +3896,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3885,6 +3910,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -4585,7 +4611,7 @@
"version": "22.18.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.11.tgz",
"integrity":
"sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -4610,6 +4636,16 @@
"@types/react": "^19.2.0"
}
},
+ "node_modules/@types/sax": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
+ "integrity":
"sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -6666,7 +6702,7 @@
"version": "2.1.2",
"resolved":
"https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity":
"sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "devOptional": true,
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -7866,6 +7902,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity":
"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -8863,7 +8900,7 @@
"version": "4.12.0",
"resolved":
"https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz",
"integrity":
"sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
@@ -10124,7 +10161,7 @@
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity":
"sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
@@ -10300,7 +10337,7 @@
"version": "1.30.1",
"resolved":
"https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
"integrity":
"sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
- "devOptional": true,
+ "dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -10332,6 +10369,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -10352,6 +10390,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"darwin"
@@ -10371,6 +10410,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -10390,6 +10430,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -10409,6 +10450,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -10428,6 +10470,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -10447,6 +10490,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -10466,6 +10510,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -10485,6 +10530,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"win32"
@@ -10504,6 +10550,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"optional": true,
"os": [
"linux"
@@ -13380,7 +13427,7 @@
"version": "1.0.0",
"resolved":
"https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity":
"sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
@@ -13518,6 +13565,16 @@
"integrity":
"sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/sax": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
+ "integrity":
"sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
@@ -13929,6 +13986,43 @@
"integrity":
"sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
"dev": true
},
+ "node_modules/sitemap": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz",
+ "integrity":
"sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^24.9.2",
+ "@types/sax": "^1.2.1",
+ "arg": "^5.0.0",
+ "sax": "^1.4.1"
+ },
+ "bin": {
+ "sitemap": "dist/esm/cli.js"
+ },
+ "engines": {
+ "node": ">=20.19.5",
+ "npm": ">=10.8.2"
+ }
+ },
+ "node_modules/sitemap/node_modules/@types/node": {
+ "version": "24.12.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
+ "integrity":
"sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/sitemap/node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved":
"https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity":
"sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/source-map": {
"version": "0.6.1",
"resolved":
"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -14469,7 +14563,7 @@
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
"integrity":
"sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.27.0",
@@ -14492,6 +14586,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14508,6 +14603,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14524,6 +14620,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14540,6 +14637,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14556,6 +14654,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14572,6 +14671,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14588,6 +14688,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14604,6 +14705,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14620,6 +14722,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14636,6 +14739,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14652,6 +14756,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14668,6 +14773,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14684,6 +14790,7 @@
"cpu": [
"mips64el"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14700,6 +14807,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14716,6 +14824,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14732,6 +14841,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14748,6 +14858,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14764,6 +14875,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14780,6 +14892,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14796,6 +14909,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14812,6 +14926,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14828,6 +14943,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14844,6 +14960,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14860,6 +14977,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14876,6 +14994,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14892,6 +15011,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -14905,7 +15025,7 @@
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
"integrity":
"sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
- "devOptional": true,
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -15131,7 +15251,7 @@
"version": "6.21.0",
"resolved":
"https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity":
"sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/unified": {
@@ -15613,6 +15733,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15629,6 +15750,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15645,6 +15767,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15661,6 +15784,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15677,6 +15801,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15693,6 +15818,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15709,6 +15835,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15725,6 +15852,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15741,6 +15869,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15757,6 +15886,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15773,6 +15903,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15789,6 +15920,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15805,6 +15937,7 @@
"cpu": [
"mips64el"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15821,6 +15954,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15837,6 +15971,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15853,6 +15988,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15869,6 +16005,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15885,6 +16022,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15901,6 +16039,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15917,6 +16056,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15933,6 +16073,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15949,6 +16090,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15965,6 +16107,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15981,6 +16124,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -15997,6 +16141,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -16013,6 +16158,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
diff --git a/hbase-website/package.json b/hbase-website/package.json
index df0766bb765..ce3a8297765 100644
--- a/hbase-website/package.json
+++ b/hbase-website/package.json
@@ -25,11 +25,12 @@
"extract-hbase-config": "node scripts/extract-hbase-config.js",
"extract-hbase-version": "node scripts/extract-hbase-version.js",
"extract-all": "node scripts/extract-developers.js && node
scripts/extract-hbase-config.js && node scripts/extract-hbase-version.js",
+ "generate-sitemap": "tsx scripts/generate-sitemap.ts",
"export-pdf": "playwright test e2e-tests/export-pdf.spec.ts
--project=chromium --workers=1",
"fumadocs-init": "fumadocs-mdx",
"copy-pdf-to-build": "cp -r public/books build/client",
- "ci": "npm run extract-all && npm run fumadocs-init && npm run lint && npm
run typecheck && npm run test:unit:run && npm run build && npx playwright
install chromium firefox && npm run test:e2e && npm run copy-pdf-to-build",
- "ci-skip-tests": "npm run extract-all && npm run fumadocs-init && npm run
build && npx playwright install chromium && npm run export-pdf && npm run
copy-pdf-to-build"
+ "ci": "npm run extract-all && npm run fumadocs-init && npm run lint && npm
run typecheck && npm run test:unit:run && npm run build && npm run
generate-sitemap && npx playwright install chromium firefox && npm run test:e2e
&& npm run copy-pdf-to-build",
+ "ci-skip-tests": "npm run extract-all && npm run fumadocs-init && npm run
build && npm run generate-sitemap && npx playwright install chromium && npm run
export-pdf && npm run copy-pdf-to-build"
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
@@ -110,6 +111,7 @@
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"serve": "^14.2.6",
+ "sitemap": "^9.0.1",
"tailwindcss": "^4.1.13",
"tsx": "^4.21.0",
"tw-animate-css": "1.3.3",
diff --git a/hbase-website/public/404.html b/hbase-website/public/404.html
new file mode 100644
index 00000000000..00740830f00
--- /dev/null
+++ b/hbase-website/public/404.html
@@ -0,0 +1,245 @@
+<!doctype html>
+<!--
+ 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.
+-->
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="robots" content="noindex, nofollow" />
+ <title>Page Not Found | Apache HBase</title>
+ <style>
+ @font-face {
+ font-family: Inter;
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+ src: url("/fonts/inter-latin-wght-normal.woff2") format("woff2");
+ }
+
+ :root {
+ color-scheme: light;
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --border: oklch(0.922 0 0);
+ --primary: oklch(50.312% 0.19572 29.534);
+ --primary-strong: oklch(50.312% 0.19572 29.534);
+ --primary-foreground: oklch(0.985 0 0);
+ --ring: oklch(0.708 0 0);
+ }
+
+ @media (prefers-color-scheme: dark) {
+ :root {
+ color-scheme: dark;
+ --background: oklch(0.17 0 0);
+ --foreground: oklch(0.92 0 0);
+ --muted-foreground: oklch(0.62 0 0);
+ --border: oklch(0.3 0 0);
+ --primary: oklch(54% 0.15 29.5);
+ --primary-strong: oklch(53% 0.17 29.5);
+ --primary-foreground: oklch(97% 0 0);
+ --ring: oklch(50.312% 0.19572 29.534);
+ }
+ }
+
+ html.light {
+ color-scheme: light;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --border: oklch(0.922 0 0);
+ --primary: oklch(50.312% 0.19572 29.534);
+ --primary-strong: oklch(50.312% 0.19572 29.534);
+ --primary-foreground: oklch(0.985 0 0);
+ --ring: oklch(0.708 0 0);
+ }
+
+ html.dark {
+ color-scheme: dark;
+ --background: oklch(0.17 0 0);
+ --foreground: oklch(0.92 0 0);
+ --muted-foreground: oklch(0.62 0 0);
+ --border: oklch(0.3 0 0);
+ --primary: oklch(54% 0.15 29.5);
+ --primary-strong: oklch(53% 0.17 29.5);
+ --primary-foreground: oklch(97% 0 0);
+ --ring: oklch(50.312% 0.19572 29.534);
+ }
+
+ * {
+ box-sizing: border-box;
+ }
+
+ body {
+ min-height: 100vh;
+ margin: 0;
+ background:
+ radial-gradient(circle at top, rgba(186, 22, 12, 0.08), transparent
32rem),
+ var(--background);
+ color: var(--foreground);
+ font-family:
+ Inter,
+ ui-sans-serif,
+ system-ui,
+ -apple-system,
+ BlinkMacSystemFont,
+ "Segoe UI",
+ sans-serif;
+ }
+
+ main {
+ display: grid;
+ min-height: 100vh;
+ place-items: center;
+ padding: 2rem;
+ }
+
+ .content {
+ width: min(100%, 42rem);
+ padding: clamp(2rem, 6vw, 4rem);
+ text-align: center;
+ }
+
+ .logo {
+ width: 9rem;
+ height: auto;
+ margin-bottom: 2rem;
+ }
+
+ .eyebrow {
+ margin: 0;
+ color: var(--muted-foreground);
+ font-size: 1rem;
+ font-weight: 700;
+ letter-spacing: 0.3em;
+ text-transform: uppercase;
+ }
+
+ h1 {
+ margin: 1rem 0 0;
+ font-size: clamp(2.5rem, 8vw, 4.5rem);
+ line-height: 1;
+ letter-spacing: -0.05em;
+ }
+
+ .message {
+ margin: 1.25rem auto 0;
+ max-width: 34rem;
+ color: var(--muted-foreground);
+ font-size: clamp(1.125rem, 2vw, 1.25rem);
+ line-height: 1.65;
+ }
+
+ .actions {
+ display: flex;
+ justify-content: center;
+ margin-top: 2rem;
+ }
+
+ .button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ height: 2.5rem;
+ padding: 0.5rem 1.5rem;
+ border: 1px solid transparent;
+ border-radius: calc(var(--radius) - 2px);
+ background: var(--primary);
+ color: var(--primary-foreground);
+ cursor: pointer;
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ text-decoration: none;
+ white-space: nowrap;
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ outline: none;
+ transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
+ }
+
+ .button:hover {
+ background: color-mix(in oklch, var(--primary) 90%, transparent);
+ }
+
+ html.dark .button {
+ background: var(--primary-strong);
+ }
+
+ html.dark .button:hover {
+ background: color-mix(in oklch, var(--primary-strong) 90%,
transparent);
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .button {
+ background: var(--primary-strong);
+ }
+
+ .button:hover {
+ background: color-mix(in oklch, var(--primary-strong) 90%,
transparent);
+ }
+ }
+
+ html.light .button {
+ background: var(--primary);
+ }
+
+ html.light .button:hover {
+ background: color-mix(in oklch, var(--primary) 90%, transparent);
+ }
+
+ .button:focus-visible {
+ border-color: var(--ring);
+ box-shadow:
+ 0 1px 2px 0 rgb(0 0 0 / 0.05),
+ 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
+ }
+ </style>
+ <script>
+ (function () {
+ try {
+ var theme = localStorage.getItem("theme");
+ var root = document.documentElement;
+
+ if (theme === "light" || theme === "dark") {
+ root.classList.add(theme);
+ }
+ } catch (_) {
+ // Keep the CSS prefers-color-scheme fallback if storage is
unavailable.
+ }
+ })();
+ </script>
+ </head>
+ <body>
+ <main>
+ <section class="content" aria-labelledby="page-title">
+ <img class="logo" src="/images/logo.svg" alt="Apache HBase" />
+ <p class="eyebrow">404</p>
+ <h1 id="page-title">Page not found</h1>
+ <p class="message">
+ The website was updated recently, so the route you were trying to
visit might have
+ changed.
+ </p>
+ <div class="actions">
+ <a class="button" href="/">Go back home</a>
+ </div>
+ </section>
+ </main>
+ </body>
+</html>
diff --git a/hbase-website/public/robots.txt b/hbase-website/public/robots.txt
new file mode 100644
index 00000000000..ddb46af05bd
--- /dev/null
+++ b/hbase-website/public/robots.txt
@@ -0,0 +1,5 @@
+User-agent: *
+Allow: /
+Disallow: /404.html
+
+Sitemap: https://hbase.apache.org/sitemap.xml
diff --git a/hbase-website/scripts/generate-sitemap.ts
b/hbase-website/scripts/generate-sitemap.ts
new file mode 100644
index 00000000000..2d441776e2d
--- /dev/null
+++ b/hbase-website/scripts/generate-sitemap.ts
@@ -0,0 +1,116 @@
+//
+// 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.
+//
+
+import { access, glob, readFile, writeFile } from "node:fs/promises";
+import { join } from "node:path";
+import { fileURLToPath } from "node:url";
+import { SitemapStream, streamToPromise } from "sitemap";
+
+const ROOT = join(import.meta.dirname, "..");
+const PUBLIC_DIR = join(ROOT, "public");
+const BUILD_DIR = join(ROOT, "build", "client");
+const PUBLIC_SITEMAP_PATH = join(PUBLIC_DIR, "sitemap.xml");
+const BUILD_SITEMAP_PATH = join(BUILD_DIR, "sitemap.xml");
+const SITE_URL = "https://hbase.apache.org";
+
+const EXCLUDED_HTML_PATHS = new Set(["404.html", "__spa-fallback.html"]);
+
+const REDIRECT_PAGE_PATTERNS = [
+ /window\.location\.replace\(/i,
+ /http-equiv=["']refresh/i,
+ />Redirecting to .*?If it does not happen automatically,/is
+];
+
+interface SitemapEntry {
+ url: string;
+}
+
+export function normalizePath(relativePath: string): string {
+ return relativePath.replaceAll("\\", "/");
+}
+
+export function toSiteUrl(relativePath: string): string {
+ if (relativePath === "index.html") {
+ return "/";
+ }
+
+ if (relativePath.endsWith("/index.html")) {
+ return `/${relativePath.slice(0, -"/index.html".length)}/`;
+ }
+
+ return `/${relativePath}`;
+}
+
+export function isRedirectOnlyPage(html: string): boolean {
+ return REDIRECT_PAGE_PATTERNS.some((pattern) => pattern.test(html));
+}
+
+export function shouldIncludeInSitemap(relativePath: string, html: string):
boolean {
+ if (EXCLUDED_HTML_PATHS.has(relativePath)) {
+ return false;
+ }
+
+ return !isRedirectOnlyPage(html);
+}
+
+export async function collectSitemapEntries(): Promise<SitemapEntry[]> {
+ const entries: SitemapEntry[] = [];
+
+ for await (const htmlPath of glob("**/*.html", { cwd: BUILD_DIR })) {
+ const relativePath = normalizePath(htmlPath);
+ const filePath = join(BUILD_DIR, relativePath);
+ const html = await readFile(filePath, "utf8");
+
+ if (!shouldIncludeInSitemap(relativePath, html)) {
+ continue;
+ }
+
+ entries.push({
+ url: toSiteUrl(relativePath)
+ });
+ }
+
+ entries.sort((left, right) => left.url.localeCompare(right.url));
+ return entries;
+}
+
+export async function main() {
+ await access(BUILD_DIR);
+
+ const entries = await collectSitemapEntries();
+ const sitemap = new SitemapStream({ hostname: SITE_URL });
+
+ for (const entry of entries) {
+ sitemap.write(entry);
+ }
+
+ sitemap.end();
+
+ const xml = `${(await streamToPromise(sitemap)).toString().trimEnd()}\n`;
+ await Promise.all([writeFile(PUBLIC_SITEMAP_PATH, xml),
writeFile(BUILD_SITEMAP_PATH, xml)]);
+
+ console.log(
+ `Generated sitemap with ${entries.length} URLs at ${PUBLIC_SITEMAP_PATH}
and ${BUILD_SITEMAP_PATH}`
+ );
+}
+
+const isDirectRun = process.argv[1] === fileURLToPath(import.meta.url);
+
+if (isDirectRun) {
+ await main();
+}
diff --git a/hbase-website/unit-tests/generate-sitemap.test.ts
b/hbase-website/unit-tests/generate-sitemap.test.ts
new file mode 100644
index 00000000000..8dcf0e41625
--- /dev/null
+++ b/hbase-website/unit-tests/generate-sitemap.test.ts
@@ -0,0 +1,90 @@
+//
+// 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.
+//
+
+import { describe, expect, it } from "vitest";
+import {
+ isRedirectOnlyPage,
+ normalizePath,
+ shouldIncludeInSitemap,
+ toSiteUrl
+} from "../scripts/generate-sitemap";
+
+describe("normalizePath", () => {
+ it("preserves forward-slash paths", () => {
+ expect(normalizePath("docs/index.html")).toBe("docs/index.html");
+ });
+
+ it("converts windows separators to forward slashes", () => {
+
expect(normalizePath("docs\\configuration\\index.html")).toBe("docs/configuration/index.html");
+ });
+});
+
+describe("toSiteUrl", () => {
+ it("maps the root index file to slash", () => {
+ expect(toSiteUrl("index.html")).toBe("/");
+ });
+
+ it("maps nested index files to trailing-slash URLs", () => {
+
expect(toSiteUrl("docs/configuration/index.html")).toBe("/docs/configuration/");
+ });
+
+ it("preserves non-index html filenames", () => {
+ expect(toSiteUrl("llms-full.txt.html")).toBe("/llms-full.txt.html");
+ });
+});
+
+describe("isRedirectOnlyPage", () => {
+ it("detects javascript redirect pages", () => {
+
expect(isRedirectOnlyPage('<script>window.location.replace("/docs/")</script>')).toBe(true);
+ });
+
+ it("detects meta refresh redirects", () => {
+ expect(isRedirectOnlyPage('<meta http-equiv="refresh" content="0;
url=/downloads/" />')).toBe(
+ true
+ );
+ });
+
+ it("does not flag ordinary html pages", () => {
+ expect(isRedirectOnlyPage("<html><body><h1>Apache
HBase</h1></body></html>")).toBe(false);
+ });
+});
+
+describe("shouldIncludeInSitemap", () => {
+ it("excludes explicitly ignored html paths", () => {
+ expect(shouldIncludeInSitemap("404.html", "<html></html>")).toBe(false);
+ expect(shouldIncludeInSitemap("__spa-fallback.html",
"<html></html>")).toBe(false);
+ });
+
+ it("excludes redirect-only pages even if the path is not prelisted", () => {
+ expect(
+ shouldIncludeInSitemap(
+ "docs/redirect/index.html",
+ '<script>window.location.replace("/docs/")</script>'
+ )
+ ).toBe(false);
+ });
+
+ it("includes normal prerendered pages", () => {
+ expect(
+ shouldIncludeInSitemap(
+ "docs/configuration/index.html",
+ "<html><body><h1>Configuration</h1></body></html>"
+ )
+ ).toBe(true);
+ });
+});
diff --git a/pom.xml b/pom.xml
index b782a160311..e47de81da76 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2144,6 +2144,8 @@
<exclude>**/public/old-book-static-files/hbase.css</exclude>
<!-- http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT
License -->
<exclude>**/public/old-book-static-files/font-awesome.css</exclude>
+ <!-- Website SEO metadata file cannot carry an ASF license
header. -->
+ <exclude>**/public/robots.txt</exclude>
<!-- vector graphics -->
<exclude>**/*.vm</exclude>
<!-- apache doxia generated -->