How to develop and test the theme locally before publishing to npm.
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ test-site/ │ │ file: protocol │ │ npm install │
│ (local path) │ --> │ (simulates npm) │ --> │ (production) │
│ instant HMR │ │ needs build │ │ needs publish │
└─────────────────┘ └──────────────────┘ └─────────────────┘
packages/
├── docusaurus-theme/ # @netfoundry/docusaurus-theme npm package
│ ├── src/ # TypeScript source → compiled to dist/
│ ├── css/ # Stylesheets (served directly, not compiled)
│ └── theme/ # Docusaurus theme component overrides
└── test-site/ # Aggregator test site (mirrors unified-doc)
├── remotes/ # Fake sub-projects (like unified-doc/_remotes)
│ ├── zrok/website/
│ ├── openziti/docusaurus/
│ ├── frontdoor/docusaurus/
│ ├── onprem/docusaurus/
│ └── zlan/docusaurus/
├── docs/ # Main docs (classic preset)
└── docusaurus.config.ts # Aggregates all sub-projects
The test-site mirrors the unified-doc architecture: one Docusaurus site aggregating
multiple standalone sub-projects via plugin-content-docs instances. Each sub-project
lives in remotes/ with its own docusaurus.config.ts (for standalone use) and a
docusaurus-plugin-*-docs.ts (for aggregation).
The test-site references the theme via local path. Start the dev server:
cd packages/test-site
yarn start| What you changed | What to do |
|---|---|
src/ components (React/TS) |
Nothing — webpack HMR picks them up instantly |
theme/ component overrides |
Rebuild needed — see watch mode below |
css/ stylesheets |
Rebuild needed — see below |
theme/ overrides (e.g. swizzled Docusaurus components) are compiled to dist/theme/
and served from there. Run the TypeScript compiler in watch mode in a second terminal
so changes are recompiled automatically:
# Terminal 1 — theme watcher
cd packages/docusaurus-theme
yarn watch
# Terminal 2 — test site dev server
cd packages/test-site
yarn startOn every save in theme/, tsc --watch recompiles in ~1 second and Docusaurus
hot-reloads the result.
Theme CSS files (packages/docusaurus-theme/css/) are not watched automatically.
After editing CSS run a full build:
cd packages/docusaurus-theme
yarn buildThe dev server will pick up the rebuilt CSS on next page load.
Before testing with remote sites or publishing:
cd packages/docusaurus-theme
yarn buildThis compiles TypeScript to CommonJS in dist/.
This simulates exactly what npm publish will deliver.
{
"dependencies": {
"@netfoundry/docusaurus-theme": "file:../../../../packages/docusaurus-theme"
}
}Adjust the path based on your remote's location relative to the theme package.
cd unified-doc/_remotes/frontdoor/docusaurus
yarn install
yarn build # production build
# or
yarn start # dev serverAfter making theme changes:
# 1. Rebuild theme
cd packages/docusaurus-theme
yarn build
# 2. Reinstall in remote (force to pick up changes)
cd unified-doc/_remotes/frontdoor/docusaurus
yarn install --force
# 3. Clear cache if you see stale behavior
rm -rf .docusaurus node_modules/.cache
# 4. Test
yarn buildOnce satisfied with local testing:
cd packages/docusaurus-theme
yarn build
yarn test
npm version patch # or minor/major
npm publishChange the remote's package.json back to a version number:
{
"dependencies": {
"@netfoundry/docusaurus-theme": "^0.2.3"
}
}Then:
cd unified-doc/_remotes/frontdoor/docusaurus
rm -rf node_modules/@netfoundry
yarn install
yarn build| Stage | Theme build? | Command |
|---|---|---|
Dev with test-site (src/ components) |
No | cd packages/test-site && yarn start |
Dev with test-site (theme/ overrides) |
Watch mode | cd packages/docusaurus-theme && yarn watch |
| Dev with test-site (CSS) | Yes | cd packages/docusaurus-theme && yarn build |
| Test with file: | Yes | yarn build then remote yarn install --force |
| Publish | Yes | npm version patch && npm publish |
| Verify from npm | N/A | remote yarn install && yarn build |
Each sub-project in remotes/ follows the same pattern as the real repos:
remotes/zrok/website/
├── docusaurus.config.ts # Standalone config (references local theme)
├── docusaurus-plugin-zrok-docs.ts # Plugin config for aggregation
├── sidebars.ts
├── static/
└── docs/
└── *.mdx
The aggregator config (test-site/docusaurus.config.ts) imports each plugin:
import {zrokDocsPluginConfig} from "./remotes/zrok/website/docusaurus-plugin-zrok-docs";
// ...
plugins: [
zrokDocsPluginConfig(zrokRoot, REMARK_MAPPINGS, 'docs/zrok'),
]All sub-projects route under /docs/$projectname.
# Clear all caches
rm -rf .docusaurus node_modules/.cache
yarn install --force# Ensure theme is rebuilt
cd packages/docusaurus-theme && yarn build
# Force reinstall in remote
cd <remote> && yarn install --forceYou're importing from /ui in a config file. Use /config instead:
// Wrong - has CSS, fails at config load time
import { foo } from "@netfoundry/docusaurus-theme/ui";
// Right - no CSS, safe for config files
import { foo } from "@netfoundry/docusaurus-theme/config";Config files are loaded by jiti which can't parse JSX. Use .ts not .tsx for files
imported by docusaurus.config.ts, and use plain objects instead of JSX:
// Wrong
documentationLinks: [<a href="/docs">Docs</a>]
// Right
documentationLinks: [{ href: '/docs', label: 'Docs' }]