Docs Site — Operations
How to run, build, deploy, and version docs.nquiry.ai — the Docusaurus-rendered docs site that serves docs/guide/, docs/admin/, and docs/reference/ to the public.
Source layout
The docs site lives in docs-site/ inside this repo. It does not copy the markdown — it points plugin-content-docs at ../docs/guide/, ../docs/admin/, ../docs/reference/. Editing a .md file in docs/ and rebuilding the site picks up the change.
investigation-app/
├── docs/ ← markdown source (3 sections)
│ ├── guide/ ← end-user
│ ├── admin/ ← operator / licensee
│ └── reference/ ← technical
└── docs-site/ ← Docusaurus app
├── docusaurus.config.js ← maps the 3 sections to URL paths
├── sidebars/ ← autogenerated per-section sidebars
├── src/ ← landing page + theme overrides
└── static/ ← favicon, images
Run locally
cd docs-site
npm install # first time
npm run start # serves at http://localhost:3000 with hot reload
The dev server hot-reloads when you edit any .md under docs/. If you change docusaurus.config.js, restart the server.
Build
cd docs-site
npm run build # outputs to docs-site/build/
npm run serve # serve the built site locally on :3000 (sanity check)
Broken-link warnings appear during build. They are warnings, not errors — the build succeeds and produces the site. Fix them in the source markdown when you find them; the weekly freshness sweep (NQU-670) also flags them.
Deploy
Code-wired in PR 2, but not yet live. PR 2 lands the terraform module (
infrastructure/terraform/modules/docs-site/) and the GitHub Actions workflow (.github/workflows/docs-site-deploy.yml). The resources don't exist in AWS untilterraform applyruns againstenvironments/dev. PR 3 runs the apply and wires DNS fordocs.nquiry.ai. Until PR 3 applies, the workflow will fail at theaws s3 syncstep (target bucket doesn't exist yet).
How a deploy happens (after PR 3 lands)
- Merge to
mainwith changes in any ofdocs/guide/**,docs/admin/**,docs/reference/**,docs-site/**, or.github/workflows/docs-site-deploy.yml - The
Docs Site Deployworkflow fires (concurrency-grouped — newer commits cancel in-flight builds) - Workflow:
npm ci+npm run buildinsidedocs-site/(~3 min) aws s3 sync docs-site/build/ s3://<bucket>/ --delete --cache-control "public,max-age=3600"aws cloudfront create-invalidation --distribution-id <id> --paths "/*"- CloudFront edge cache refreshed;
https://docs.nquiry.ai/reflects the new content within 1–2 minutes
Required GitHub Actions secrets
| Secret | Value | Source |
|---|---|---|
AWS_ROLE_ARN | Already exists | Output of cicd module — same role used by app CI |
DOCS_SITE_BUCKET | Set after PR 3 apply | terraform output -raw docs_site_bucket_name |
DOCS_SITE_DISTRIBUTION_ID | Set after PR 3 apply | terraform output -raw docs_site_distribution_id |
Manual deploy (operator-driven)
# 1. Build locally
cd docs-site
npm install
npm run build
# 2. Sync
aws s3 sync build/ "s3://$(cd ../infrastructure/terraform/environments/dev && terraform output -raw docs_site_bucket_name)/" \
--delete --cache-control "public,max-age=3600"
# 3. Invalidate
aws cloudfront create-invalidation \
--distribution-id "$(cd ../infrastructure/terraform/environments/dev && terraform output -raw docs_site_distribution_id)" \
--paths "/*"
Version cuts
Docusaurus's versioned-docs feature is not enabled in v1. When the app gets its first SLA-bound version cut, run:
cd docs-site
npm run docusaurus docs:version 0.4 # snapshots current docs as v0.4
This copies docs/ into docs-site/versioned_docs/version-0.4/ and freezes it. New work continues in docs/ (which becomes "Next"). The CloudFront site serves both at /v0.4/ and /next/.
Versioning is intentionally deferred until needed — once enabled, every doc change carries the cognitive cost of "which version does this go into?" Holding off until the first real customer-version boundary requires it.
Search
Search is wired via @easyops-cn/docusaurus-search-local — a FlexSearch-based plugin that builds the index at build time and ships it as a static search-index.json. Zero infrastructure; works offline.
If the corpus gets large enough that local search feels slow, the swap path is Algolia DocSearch (free for open-source/docs projects) or a Lambda + OpenSearch backend. Decision deferred per NQU-715 acceptance.
Common operations
| Task | Command (from docs-site/) |
|---|---|
| Add a new section | Add a plugin-content-docs instance in docusaurus.config.js, point at the new docs/<name>/ dir, add a sidebar file, add navbar entry |
| Change site title / tagline | Edit title / tagline in docusaurus.config.js |
| Add a top-nav link | Edit themeConfig.navbar.items in docusaurus.config.js |
| Add a footer column | Edit themeConfig.footer.links in docusaurus.config.js |
| Customize landing | Edit src/pages/index.js and src/pages/index.module.css |
| Brand colors / fonts | Edit src/css/custom.css (Infima CSS variables) |
| Force-rebuild the search index | npm run clear && npm run build |
Troubleshooting
MDX compilation error on a doc. Docusaurus 3 parses .md as CommonMark by default in this repo (markdown.format: 'detect'). If the error mentions JSX or "unexpected character," the file is being parsed as MDX — confirm the extension is .md not .mdx, and that the file doesn't open with ---\n followed by JSX-looking content.
Broken-link build warning. The link target doesn't exist in any of the 3 indexed sections (docs/guide, docs/admin, docs/reference). Fix the link in source. Note: links to docs outside those three sections (e.g. docs/working/, docs/decisions/) won't resolve — those dirs aren't published to the site.
Plugin global data not found for "docusaurus-plugin-content-docs" plugin with id "default". The search plugin needs docsPluginIdForPreferredVersion set to one of our actual plugin IDs (currently 'guide'). Already configured.
Related issues + memories
- NQU-715 — Docs site stand-up (this work)
- NQU-714 — IA proposal that defined the 3-section taxonomy
- NQU-670 — Weekly Documentation Freshness Report
docs/admin/docs-maintenance.md— manual freshness procedures