Skip to main content

PLAN: Install Docusaurus

IMPLEMENTATION RULES: Before implementing this plan, read and follow:

Status: Completed (2026-05-12)

Goal: Get Docusaurus building and serving Atlas's existing website/docs/ content locally, then ship it to GitHub Pages with custom domain atlas.sovereignsky.no (mirrors UIS's uis.sovereignsky.no pattern).

Last Updated: 2026-05-11

Investigation: INVESTIGATE-deployment-pipeline.md — this is the first PLAN spawned from it; closes the "Docusaurus skeleton + deploy" item in the recommended v1 priority order. Subsequent PLANs handle Scalar-at-/api and dbt-docs-at-/lineage/.

Prerequisites: None. Atlas-side install can ship before UIS-side deploy wiring is ready.

Reference: The UIS repo runs the same Docusaurus pattern at urbalurba-infrastructure/website/. Copy config conventions, version pins, and deploy shape from there. The conventions are already captured in atlas/website/README.md — this PLAN executes them.


Problem Summary

website/docs/ has real content (ai-developer guides, contributors guides, the public docs scaffold) but no renderer. Browsing is GitHub-only. Per the deployment-pipeline INVESTIGATE, the v1 public Atlas surface is a single Docusaurus site at atlas.sovereignsky.no that will later host Scalar at /api and dbt-docs at /lineage/. This PLAN gets the bare skeleton up — landing page, navigation, search — so the follow-on PLANs have something to mount their pages into.


Phase 1: Local install + config

Get npm run start working in website/ showing existing docs/ content with the UIS-matched conventions (Docusaurus 3.9.2, TypeScript config, search, mermaid).

Tasks

  • 1.1 Create website/package.json with "name": "atlas-website", "private": true, the script block (start, build, serve, clear, typecheck, swizzle), and the dependency set pinned to UIS's versions:
    @docusaurus/core 3.9.2
    @docusaurus/preset-classic 3.9.2
    @docusaurus/theme-mermaid 3.9.2
    @easyops-cn/docusaurus-search-local ^0.46.1
    @mdx-js/react ^3.0.0
    clsx ^2.0.0
    docusaurus-plugin-image-zoom ^2.0.0
    prism-react-renderer ^2.3.0
    react ^18.3.1
    react-dom ^18.3.1
    Dev deps: @docusaurus/module-type-aliases 3.9.2, @docusaurus/tsconfig 3.9.2, @docusaurus/types 3.9.2, typescript ~5.6.2. Engines: "node": ">=18.0".
  • 1.2 Create website/docusaurus.config.ts modelled on urbalurba-infrastructure/website/docusaurus.config.ts, with Atlas-specific fields:
    • title: 'Atlas'
    • tagline: 'Open semantic layer over Norwegian public data' (or similar — draft, refine in 1.7)
    • url: 'https://atlas.helpers.no'
    • baseUrl: '/'
    • organizationName: 'helpers-no', projectName: 'atlas'
    • onBrokenLinks: 'warn', onBrokenAnchors: 'warn'
    • markdown.mermaid: true, markdown.format: 'detect'
    • themes: ['@docusaurus/theme-mermaid']
    • Plugins: docusaurus-plugin-image-zoom + @easyops-cn/docusaurus-search-local (same config block as UIS)
    • No blog preset for now (Atlas isn't running a blog). Drop the blog: {...} block from the UIS template.
    • Navbar items: Docs (left), GitHub link (right). Skip Services and Blog items.
    • Prism: same set of additional languages — bash, yaml, json, typescript, python plus sql (relevant for Atlas).
  • 1.3 Create website/sidebars.ts with a hand-curated tutorialSidebar covering the existing docs/ shape: index, about/*, sector/*, getting-started/*, concepts/*, measurements/*, sources/*, contributors/*, ai-developer/* (use autogenerated with a dirName per category). Match the UIS pattern where each top-level category uses autogenerated against the matching folder.
  • 1.4 Create website/tsconfig.json extending @docusaurus/tsconfig.
  • 1.5 Create website/src/css/custom.css — minimal stub (Atlas can refine later). Include just the default Docusaurus CSS variables for now.
  • 1.6 Create website/static/ with a placeholder img/favicon.ico (can use a transparent 1×1 ICO or the Docusaurus default; replace later with branded asset).
  • 1.7 Make sure website/docs/index.md works as the docs landing page. If it doesn't exist yet, add a minimal one ("Atlas — open semantic layer over Norwegian public data. Browse the docs in the sidebar.").
  • 1.8 Create website/.gitignore to exclude node_modules/, build/, .docusaurus/, .cache-loader/.
  • 1.9 cd website && npm install — let it resolve. Commit package.json + package-lock.json.
  • 1.10 npm run start — verify the dev server boots on port 3000 and renders the landing page + sidebar navigation. Click through into ai-developer/, contributors/, etc.; confirm pages render.
  • 1.11 Fix any onBrokenLinks: 'warn' warnings in the existing markdown that surface during build — minimum: nothing that's actually broken. Acceptable to leave warnings that point at files added in later PLANs.

Validation

npm run start runs in website/; the dev server at http://localhost:3000 renders the landing page + sidebar, and clicking into existing docs/ content (e.g. ai-developer/PLANS.md, contributors/setup.md) navigates correctly. Search box at the top works. Mermaid diagrams in any existing markdown render.

Phase 1 outcome (2026-05-11)

Shipped. npm run build succeeds; npm run serve returns 200 on /, /about/what-is-atlas, /contributors/setup, /ai-developer/PLANS.

Three deviations from the PLAN as written, each justified:

  • Webpack pin via overrides — Atlas's fresh install resolved webpack 5.106.2, which has a ProgressPlugin schema regression incompatible with webpackbar (the build fails with options has an unknown property 'name' / 'color' / 'reporters'). UIS's lockfile resolves to 5.104.1 which works. Added "overrides": { "webpack": "5.104.1" } to package.json to match. Worth revisiting when Docusaurus releases a build that pins webpack itself.
  • Stub index.md added to concepts/, measurements/, sources/ — task 1.3 assumed autogenerated sidebars would work for empty category folders (they have _category_.json but no markdown). They don't; Docusaurus emits No docs found in 'X': can't auto-generate a sidebar. Added a placeholder index.md in each — same "in progress" framing the README already uses for these categories.
  • Docs routed at /, not /docsrouteBasePath: '/' in the docs preset (plus docsRouteBasePath: '/' in the search plugin config). Without it, https://atlas.helpers.no/ would 404 and only /docs/* would resolve. Atlas is a docs-only site (no blog, no services pages), so the docs are the site — make / work. URLs simplify accordingly: /about/what-is-atlas rather than /docs/about/what-is-atlas. Does not conflict with the planned /api (Scalar) or /lineage/ (dbt-docs) subpaths because Docusaurus only claims routes that exist in docs/.

Existing markdown emits ~30 "broken link" and ~10 "broken anchor" warnings — links written for GitHub rendering ([x](path/to/file.md)) that don't resolve cleanly under Docusaurus. onBrokenLinks: 'warn' lets the build pass. These get cleaned up incrementally in content PLANs; not blocking Phase 2.

The favicon task (1.6) was effectively skipped — no favicon field set in docusaurus.config.ts, so Docusaurus uses its built-in default. Branded favicon lands later with the rest of brand assets.


Phase 2: Production build + CI gate

Build the site as static HTML, validate it in CI on every PR.

Tasks

  • 2.1 cd website && npm run build — verify the production build succeeds and produces website/build/ with index.html, hashed JS/CSS, and per-page HTML.
  • 2.2 npm run serve — verify the built static site serves correctly at http://localhost:3000 (catches any "works in dev, breaks in prod" Docusaurus quirks).
  • 2.3 Add a GitHub Actions workflow at .github/workflows/website-build.yml (or extend an existing one) that runs npm install && npm run typecheck && npm run build on every PR and push to main, in website/. No deploy yet — just the build gate. Pin node-version: 20.
  • 2.4 Verify the workflow passes on a PR.

Validation

PRs touching website/ get a green CI check that proves npm run build succeeds.

Phase 2 outcome (2026-05-12)

2.1 + 2.2 passed during Phase 1 verification; not re-run for Phase 2 because the state is unchanged. 2.3 shipped: .github/workflows/website-build.yml runs npm ci + typecheck + build on every PR and push to main that touches website/** or the workflow file itself. Pinned to node-version: 20, with npm cache keyed on website/package-lock.json — matches the pattern UIS uses in their own docs.yml. permissions: contents: read (no writes needed for a build-only gate). No deploy step here — that's Phase 3.

One deviation from the PLAN: 2.4 (verify the workflow passes on a PR) is deferred to when the branch ships. The workflow can't run until the branch is pushed to origin and a PR is opened. That's a single push + gh pr create once we're ready to ship Phase 2, not work the implementation needs to wait on.

The strict onBrokenLinks: 'throw' / onBrokenAnchors: 'throw' config from Phase 1's link-cleanup pass means this CI gate automatically catches any future broken markdown link or anchor — no separate guard needed.


Phase 3: Deploy to GitHub Pages

Pivot from "Docker image on UIS" to "GitHub Pages with custom domain" — matches the sister-project pattern (urbalurba-infrastructure ships its docs at uis.sovereignsky.no via the same path). Static-site docs don't need UIS's Kubernetes/Traefik/observability; the dynamic services (PostgREST, Dagster, atlas-data) stay on UIS.

Hostname: atlas.sovereignsky.no for v1, mirroring uis.sovereignsky.no. The original atlas.helpers.no target via UIS Traefik is deferred — possibly a future migration once helpers.no DNS is in our control, possibly never if sovereignsky.no stays the docs home.

Tasks

  • 3.1 Add the GitHub Pages deploy job to .github/workflows/website-build.yml:
    • Trigger: same as the build job (PR + push to main + workflow_dispatch), but the deploy step runs only on push to main (use an if: guard).
    • Pattern: mirror UIS's docs.ymlactions/upload-pages-artifact@v3 after the build, then a deploy job that runs actions/deploy-pages@v4.
    • Permissions: contents: read, pages: write, id-token: write (Pages deploy requires the OIDC token to authenticate).
    • Concurrency: group: "pages", cancel-in-progress: false — only one deploy at a time, queue the rest.
  • 3.2 Create website/static/CNAME containing the single line atlas.sovereignsky.no — Docusaurus copies the contents of static/ to the build root, so the deployed site has a CNAME file at the root that GitHub Pages reads to set the custom domain.
  • 3.3 Repo-settings step:
    • Repo flipped public via gh api -X PATCH /repos/terchris/atlas -f visibility=public (free-plan GitHub Pages requires public repo).
    • Pages enabled via gh api -X POST /repos/terchris/atlas/pages -f build_type=workflow.
    • Custom domain registered via gh api -X PUT /repos/terchris/atlas/pages -f cname=atlas.sovereignsky.no after first deploy.
  • 3.4 DNS step: CNAME atlas.sovereignsky.noterchris.github.io added at user's DNS provider. Verified dig atlas.sovereignsky.no +short returns the GitHub Pages IPs 185.199.108-111.153.
  • 3.5 Verified after first deploy:
    • https://terchris.github.io/atlas/ → 200 (default project-pages URL).
    • https://atlas.sovereignsky.no/ → live after Let's Encrypt cert provisions (a few minutes after CNAME registration).

Validation

https://atlas.sovereignsky.no/ is live and serves the Atlas Docusaurus site. A content change in website/docs/ → merged to main → workflow builds + deploys → visible at atlas.sovereignsky.no end-to-end.

Phase 3 outcome (2026-05-12)

Shipped end-to-end. Workflow mirrors UIS's docs.yml but stays one file (build + deploy with an if: guard so PRs build-only and only main pushes upload + deploy). Permissions extended to pages: write + id-token: write as required by actions/deploy-pages@v4. Concurrency group pages so deploys queue if main commits land back-to-back.

docusaurus.config.ts url field updated to https://atlas.sovereignsky.no so the sitemap, OG tags, and canonical URLs match the deploy target.

Two surprises during user-side setup:

  • Repo was private at start of Phase 3 — GitHub Pages on the free plan requires public repos. Flipped to public after explicit user consent. Consistent with sister projects (urbalurba-infrastructure and devcontainer-toolbox are both public).
  • CNAME file ≠ Pages custom-domain config when source is workflow — with build_type: workflow, the CNAME file in the deployed artefact does not auto-configure the Pages custom-domain field. (It does in branch-source mode.) Had to set the custom domain explicitly via gh api -X PUT /repos/terchris/atlas/pages -f cname=atlas.sovereignsky.no after the first deploy. The website/static/CNAME file we ship is still useful documentation in the artefact but it's not load-bearing for the actual Pages config. Worth noting for any future Docusaurus install following the same pattern.

Acceptance Criteria

  • cd website && npm run start shows the Atlas Docusaurus site locally with existing docs/ content navigable.
  • npm run build produces a working static site in website/build/.
  • CI runs npm run typecheck + npm run build on every PR.
  • CI on main builds + deploys the site to GitHub Pages.
  • https://atlas.sovereignsky.no/ is live and serves the Docusaurus site.

Implementation Notes

  • Convention reference: the existing atlas/website/README.md is the source of truth for "what conventions are inherited from sister Helpers sites" (Docusaurus 3.9.2, TS config, search plugin choice, etc.). Follow it; don't re-litigate.
  • Pivot from UIS-served to GitHub Pages (2026-05-12): earlier drafts of this PLAN assumed a Docker image + UIS Traefik IngressRoute. Switched to GitHub Pages with custom domain atlas.sovereignsky.no after recognising the sister-project pattern (UIS itself ships uis.sovereignsky.no this way). Trade-offs:
    • Pro: zero infra cost; no Dockerfile to maintain; no UIS-side coordination needed; automatic HTTPS via Let's Encrypt; matches what sister projects already do.
    • Con: docs are no longer "sovereign-stack-served." Acceptable because static docs don't need K8s/observability — the dynamic services that do (PostgREST, Dagster, atlas-data) remain on UIS.
    • The original UIS-served path is deferred, not deleted — could revisit if/when there's a reason to move docs onto helpers.no via UIS.
  • No developer-atlas.helpers.no: per the INVESTIGATE rewrite (2026-05-11), Scalar and dbt-docs ship as pages inside this Docusaurus site (/api and /lineage/), not separate hostnames. This PLAN does not wire up those pages; that's two follow-on PLANs. The Pages-served shape established here (single static site, single hostname) is the substrate they mount into.
  • Don't add blog/Services nav items: the UIS template has them; Atlas doesn't need them. Keep the navbar lean.
  • Mermaid is on but optional: existing Atlas markdown doesn't use Mermaid yet. Enabling it now means future plans can drop diagrams in without config changes.
  • Search index size: with current docs/ content (~50 markdown files), @easyops-cn/docusaurus-search-local's index builds in seconds and adds a few hundred KB to the bundle. Fine. Revisit if the docs corpus grows 10×.

Files to Modify / Create

Create:

  • website/package.json
  • website/package-lock.json (generated)
  • website/docusaurus.config.ts
  • website/sidebars.ts
  • website/tsconfig.json
  • website/.gitignore
  • website/.dockerignore
  • website/src/css/custom.css
  • website/static/CNAME — single line atlas.sovereignsky.no for GitHub Pages custom domain.
  • website/docs/index.md (if missing)
  • .github/workflows/website-build.yml — build gate + Pages deploy job.

Modify:

  • website/README.md — short addendum: "Run locally: npm install && npm run start in this folder." Drop the "Docusaurus is not yet installed" preamble once Phase 1 ships.
  • Top-level README.md (Atlas root) — add a link to https://atlas.sovereignsky.no once Phase 3 ships.

Do not touch:

  • Anything under urbalurba-infrastructure/. The Docusaurus deploy no longer involves UIS at all.