Nebula is a fork of the Starlight starter kit for Astro, adapted as a documentation theme for the Nebula project. This README is maintained in English and documents repository-specific details (sidebar generation, metadata conventions, and developer commands).
pnpm install
pnpm dev # start development server (default: http://localhost:4321)
pnpm build # production build -> ./dist
pnpm preview # preview built site locally
. ├── public/
├── src/
│ ├── assets/
│ ├── content/
│ │ └── docs/ # Markdown/MDX/Markdoc content files and per-folder metadata
│ ├── config/ # helper scripts (e.g. sidebar builder)
│ └── content.config.ts # Astro content collections and loaders
├── astro.config.mjs # Starlight/Nebula configuration (title, sidebar hook)
├── package.json
└── README.md
Nebula includes a small custom builder that scans src/content/docs/ and produces the Starlight sidebar structure dynamically. The builder is implemented at:
src/config/sidebar-fs.mjsastro.config.mjs calls the builder with environment flags: buildSidebarFromFS({ isDev, showReference }).src/content/docs/ to create a section._section.json, the script reads it for section metadata (label, order, devOnly, autogenerate)._section.json includes autogenerate: { "directory": "..." }, the builder emits an autogenerate sidebar entry for Starlight instead of enumerating items..md, .mdx and .mdoc files and for each content file it attempts to read a companion JSON metadata file named <base>.meta.json (e.g. example.meta.json). That metadata controls per-page label, slug, order, and devOnly.devOnly flags: pages or sections marked dev-only are included only when NODE_ENV=development or when SHOW_REFERENCE=true.Nebula supports simple companion link files to declare external links in the sidebar without editing JSON metadata:
.link: if a content file foo.mdx (or .md, .mdoc, .astro) has a sibling foo.link, the text inside foo.link is used as the sidebar URL for that entry. This takes precedence over any link/href defined in metadata. If no label is provided in metadata, the label is derived from the base filename and prettified (e.g., authoring-content → Authoring Content)..link: if foo.link exists without a content file, the builder creates a link-only sidebar item. You may still control label/order via folder metadata using the base key (foo); otherwise the label is prettified from the filename and the order defaults to 999.Note: the dev server watcher is configured to restart on .link changes. External links (http/https) in the sidebar render with an external-link icon next to the label.
Folder structure:
src/content/docs/
└─ guides/
├─ authoring-content.mdx
├─ authoring-content.link # contains: https://docs.example.com/authoring
├─ external-tool.link # contains: https://tool.example.com
└─ _metadata.json # optional label/order overrides
Resulting sidebar items under "Guides":
[
{ "label": "Authoring Content", "link": "https://docs.example.com/authoring" },
{ "label": "External Tool", "link": "https://tool.example.com" }
]
Notes:
_metadata.json provides items.authoring-content.label, that label is used instead of the prettified filename..link items can also receive label/order from _metadata.json under the same base key (e.g., items.external-tool).Per item you can set an open mode in _metadata.json (either in items.<base> or <base>):
"same" (default): open the external URL in the same tab."new": open in a new tab."embed": load the external URL inside the site chrome using an iframe wrapper.Implementation details:
/ext/<mode>/<encoded> (where <encoded> is base64url of the full URL) to implement these behaviors without relying on unsupported target/rel keys in Starlight’s sidebar.Folder-level metadata lives in a file named _section.json inside a documentation subfolder (for example src/content/docs/reference/_section.json). Fields supported:
label (string): human-friendly section label shown in the sidebar.order (number): numeric ordering priority (lower numbers appear first). Defaults to 999 when omitted.devOnly (boolean): if true the whole section is only visible when NODE_ENV=development or SHOW_REFERENCE=true.autogenerate (object): when present the builder will emit an { autogenerate: { directory: '...' } } entry to Starlight instead of enumerating per-file items. Use autogenerate.directory to point to the route/path to autogenerate.Example _section.json:
{
"label": "Reference",
"order": 20,
"devOnly": false,
"autogenerate": { "directory": "reference" }
}
Per-page metadata: for fine-grained control next to each markdown file you can add a <base>.meta.json file (for example.md use example.meta.json). Supported fields:
label (string): override the displayed label for the page.slug (string): custom route slug for the page (e.g. guides/example). If omitted the slug is inferred from folder and file name.order (number): ordering within the section (lower first). Default 999.devOnly (boolean): hide this page except when NODE_ENV=development or SHOW_REFERENCE=true.Example example.meta.json:
{
"label": "Example Guide",
"slug": "guides/example",
"order": 10,
"devOnly": false
}
Precedence and behavior notes
.md/.mdx remains the authoritative source for page metadata used at runtime (title, description, template). The JSON metadata in *.meta.json is only consulted by the sidebar builder to construct labels, slugs and ordering. When possible keep display metadata in the page frontmatter and use .meta.json only for navigation overrides.['.md', '.mdx', '.mdoc'] and ignores other files.999, and items are sorted ascending.How to preview dev-only content
Set the environment variable SHOW_REFERENCE=true when running the dev server to force include dev-only sections/pages:
$env:SHOW_REFERENCE = 'true'
pnpm dev
Or run in a single line:
$env:SHOW_REFERENCE='true'; pnpm dev
.md, .mdx y .mdoc.{% tabs %}), usa .mdoc.src/content.config.ts which wires Starlight’s docsLoader() and docsSchema() to provide typed frontmatter validation. Frontmatter inside .md/.mdx/.mdoc is the primary source of per-page metadata.*.meta.json) when present.astro.config.mjs.src/config/sidebar-fs.mjs..md, .mdx or .mdoc files under src/content/docs/ and optionally create <base>.meta.json next to the file if you need custom label, slug, order or devOnly..md — Standard Markdown..mdx — Markdown with JSX components..mdoc — Markdoc (recommended for Starlight Markdoc tags like {% tabs %}).SHOW_REFERENCE=true pnpm dev if you need to preview sections normally hidden behind environment flags.src/config/sidebar-fs.mjssrc/content.config.tsIf you want, I can also add example _section.json and *.meta.json files into the repo, or generate README examples in a docs/ folder. Tell me which you'd prefer.
This repository includes a multi-stage Dockerfile that builds the site with Node (pnpm) and serves the compiled dist/ via nginx.
Build (standard):
docker build -t nebula-docs:latest .
Build including dev-only content (set SHOW_REFERENCE=true at build time so the sidebar builder includes dev-only sections/pages):
docker build -t nebula-docs:dev --build-arg SHOW_REFERENCE=true .
Run the container (exposes nginx on container port 80):
docker run -d -p 8080:80 --name nebula-site nebula-docs:latest
# then open http://localhost:8080
Notes:
SHOW_REFERENCE flag is a build-time arg that controls the sidebar builder's dev-only inclusion; pass it with --build-arg SHOW_REFERENCE=true when building the Docker image if you want those sections baked into the static build.docker run --rm -it --entrypoint sh -v ${PWD}:/app node:18-alpine
# inside container: pnpm install && pnpm build