5.8 KiB
Dedicated Docusaurus Homepage + Native Local Search
Use this pattern when a docs-only Docusaurus site needs a product/showcase homepage and search that runs entirely from static build artifacts.
Route separation
A docs plugin with routeBasePath: '/' can coexist with src/pages/index.tsx, but no doc may also claim /.
---
id: intro
title: Documentation overview
slug: /overview
---
After moving the intro, re-check Markdown links. A link such as ./methodology from /overview/ resolves under /overview/; use ../methodology, a Docusaurus doc link, or an explicit base-path URL.
Keep footer/navbar links explicit:
{label: 'Home', to: '/'},
{label: 'Overview', to: '/overview'},
Local search without hosted services
Install at the root of a root-only pnpm workspace:
pnpm add -w @easyops-cn/docusaurus-search-local
Configure it as a theme, not a plugin:
themes: [
[
require.resolve('@easyops-cn/docusaurus-search-local'),
{
hashed: true,
indexDocs: true,
indexBlog: false,
indexPages: true,
docsRouteBasePath: ['/', 'clients'],
language: ['en'],
highlightSearchTermsOnTargetPage: true,
explicitSearchResultPath: true,
searchResultLimits: 12,
searchResultContextMaxLength: 120,
},
],
],
docsRouteBasePath must cover every docs plugin whose content should be searchable. Do not add Algolia credentials, crawler configuration, Ask AI, or another network service when the requirement is browser-local search.
One SearchBar only
The theme supplies the navbar @theme/SearchBar. Avoid importing and rendering another SearchBar on the homepage: duplicate instances can contend for the singleton autocomplete/index lifecycle and leave the UI with input text but no fetched index or result overlay.
Use a landing-page trigger instead:
const openSearch = () => {
const input = document.querySelector<HTMLInputElement>('.navbar__search-input');
if (input) {
input.focus();
return;
}
document.dispatchEvent(
new KeyboardEvent('keydown', {key: 'k', ctrlKey: true, bubbles: true}),
);
};
<button type="button" onClick={openSearch}>
Search documentation <kbd>Ctrl/⌘ + K</kbd>
</button>
Keep the navbar field itself as the sole search implementation.
Generated homepage metrics
Do not hard-code changing registry statistics into JSX. Extend the canonical content generator to emit an ignored TypeScript module:
summary = ROOT / "src" / "generated" / "registrySummary.ts"
summary.parent.mkdir(parents=True, exist_ok=True)
summary.write_text(
"export const registrySummary = "
+ json.dumps({"total": total, "researched": researched}, separators=(",", ":"))
+ " as const;\n",
encoding="utf-8",
)
Add /src/generated to .gitignore, run generation before both tsc and docusaurus build, and import from @site/src/generated/registrySummary.
Government-portal visual adaptation
When adapting an official public-sector site, copy the visual grammar rather than cloning its page:
- derive a small token set from the real site (navy, action blue, pale neutral background, white surfaces, restrained borders);
- use a locally installed font package to avoid runtime font dependencies;
- reuse only appropriate official marks with clear provenance;
- reproduce hierarchy: narrow institutional strip, white utility navigation, large editorial hero, evidence/status blocks, and dark institutional footer;
- preserve the user's established geometry preference (for Lego, globally force
border-radius: 0even if the source site uses pills); - provide dark-mode equivalents without replacing the light-first official character.
For the VSSA pattern, the useful observed tokens were approximately navy #091a5a, action blue #0b66e4, pale background #f3f6f9, and Public Sans. The official coat-of-arms SVG was suitable as a local navbar asset.
Public page unchanged: distinguish an untriggered workflow from a failed workflow
When a user cannot see locally completed changes, do not begin with runner debugging. Establish the publication chain in order:
git status --short --branch— are the changes still uncommitted?git rev-list --left-right --count HEAD...origin/main— were they pushed?- Read the newest Actions run and compare its
head_shato the intended commit. - If the newest run still points to the previous SHA, Actions did not fail; the new workflow was never triggered. Commit and push.
- Wait for the run attached to the intended SHA to reach
completed success. - Fetch the public page with
Cache-Control: no-cacheand a query marker such as?verify=<short-sha>; assert a distinctive new heading or asset, not only HTTP 200. - Verify the public
search-index.jsoncontains representative searchable terms, then run a real browser query and open a result.
This sequence prevents two common false diagnoses: treating a successful local build as a deployment, and treating an old successful Actions run as evidence that a new change was published.
Verification gate
Run all of these before declaring completion:
pnpm validate
pnpm build
test -s build/search-index.json
Then programmatically inspect the index for representative terms from:
- the main docs plugin;
- every secondary docs plugin;
- the custom homepage when
indexPages: true.
Finally, serve the production build and verify in a browser:
- desktop and mobile homepage layout;
- navbar search and keyboard shortcut;
- a query produces visible results;
- opening a result reaches the expected dossier route;
- square geometry and responsive navigation remain intact.
Publication is complete only after commit/push, a successful Gitea Actions run, and HTTP/content verification of the public homepage, overview, representative result route, and search index.