šŸ“„

Docs-Site Platform

Parallel evaluation of VitePress and Next.js as documentation site renderers — same content, two implementations

Sign in to see your personalized configuration examples Sign In

Docs-Site Platform

A demonstration and evaluation platform that runs two documentation site implementations — VitePress and Next.js — side-by-side, both rendering the exact same content from a single canonical source.

The goal: compare documentation tooling in practice before committing to one long-term approach for the Registry Platform.


What It Is

The Docs-Site Platform is not a production split between "different sites for different audiences". It is an engineering experiment with a clear question:

Which documentation site technology best fits our workflow and content structure?

Two sites run in parallel, fed by the same Markdown files, so differences in rendering, navigation, search, and developer experience are directly observable under identical content conditions.

SiteTechnologyURL
Site AVitePressvitepress.docs.registry.hochguertel.work
Site BNext.jsnextjs.docs.registry.hochguertel.work

Both sites display the same pages. Neither is "for a specific audience". The distinction is purely the rendering technology under evaluation.


How It Works

Content is written once in docs/docs-site/. The docs-sync.py tool reads a configuration file (docs-sync.yml) and generates service-specific content roots under .service-specific/. Each docs-site container reads from its own subdirectory:

flowchart LR
    canonical["docs/docs-site/\n(canonical .md files)"]
    config["docs-sync.yml\n(per-service rules)"]
    script["docs-sync.py\n(sync tool)"]
    vp[".service-specific/vitepress/\n(symlinks → .md files)"]
    nx[".service-specific/nextjs/\n(symlinks → .md as .mdx)"]

    canonical --> script
    config --> script
    script --> vp
    script --> nx

    vp --> VitePress["VitePress container\n(registry-docs-vitepress)"]
    nx --> NextJS["Next.js container\n(registry-docs-nextjs)"]

In most cases docs-sync.py creates symlinks — the canonical file is read directly, with zero duplication. A real copy is only written when frontmatter must be transformed (e.g. adding the title field that Next.js requires but is optional in VitePress):

graph TD
    canonical["canonical file\nservices/npm/developer.md"]

    subgraph decision ["docs-sync.py decision"]
        check{"frontmatter\ntransforms\nneeded?"}
    end

    symlink["symlink\n.service-specific/vitepress/…/developer.md\n→ canonical file"]
    realfile["real file\n.service-specific/nextjs/…/developer.mdx\nwith added frontmatter"]

    canonical --> check
    check -- "No change needed" --> symlink
    check -- "title/description\nmust be injected" --> realfile

When canonical files gain proper frontmatter the tool automatically switches back to symlinks on the next sync run.


Adding a New Docs-Site Implementation

Because the sync layer is fully configuration-driven, adding a third implementation (Docusaurus, Astro, Starlight, …) requires only:

  1. Add a new services entry to docs-sync.yml with the desired output_dir, include patterns, and any frontmatter_transforms.
  2. Run task sync to generate the new content root.
  3. Create the container service (Dockerfile + compose file) pointing at the new .service-specific/<name>/ directory.

No changes to canonical docs are needed.


Key Components

Canonical Source (docs/docs-site/services/)

Authoritative Markdown files. Written and edited by humans, never by tooling. Each service has its own subdirectory; content under services/registries/ contains cross-cutting guides (quickstart, authentication, architecture, troubleshooting).

.service-specific/ (generated)

Per-service content roots built by docs-sync.py. Never edit by hand — fully regenerated on every task sync run.

docs-sync.py

The sync engine at scripts/docs-sync.py. A self-contained PEP 723 script:

CommandPurpose
syncBuild/rebuild .service-specific/ from canonical sources
generateGenerate site-specific files (homepage, config) from site-config.yml
buildgenerate + sync in one pass
statusShow per-service sync state table
checkValidate all symlinks resolve (CI-friendly, exits 1 on failure)

docs-sync.yml

Drives the sync engine. Defines for each service: which files to include/exclude, whether to rename extensions (.md → .mdx), and what frontmatter fields to add, override, or strip.

site-config.yml

Central metadata (site name, domain, registry URLs). The generate command reads this and produces the VitePress homepage (index.md) and the Next.js runtime config.yaml automatically.


Directory Layout

docs/docs-site/
│
ā”œā”€ā”€ docs-sync.yml             ← sync configuration
ā”œā”€ā”€ site-config.yml           ← site metadata (used by `generate`)
│
└── services/
    ā”œā”€ā”€ index.md              ← services landing page
    ā”œā”€ā”€ registries/           ← cross-cutting registry guides
    │   ā”œā”€ā”€ index.md
    │   └── guides/
    │       ā”œā”€ā”€ architecture.md
    │       ā”œā”€ā”€ authentication.md
    │       ā”œā”€ā”€ quickstart.md
    │       └── troubleshooting.md
    ā”œā”€ā”€ npm/                  ← npm Registry (Verdaccio) docs
    ā”œā”€ā”€ docker/               ← Docker Registry docs
    ā”œā”€ā”€ maven/                ← Maven (Reposilite) docs
    ā”œā”€ā”€ pypi/                 ← PyPI (DevPI) docs
    └── docs-site-platform/   ← this documentation (you are here)
        ā”œā”€ā”€ index.md
        ā”œā”€ā”€ developer.md      ← contributing to docs
        ā”œā”€ā”€ devops.md         ← operating the containers
        ā”œā”€ā”€ technical-writer.md
        └── guides/
            └── content-structure.md

.service-specific/            ← GENERATED — never edit
ā”œā”€ā”€ vitepress/services/       ← symlinks for VitePress srcDir
└── nextjs/services/          ← .mdx symlinks for Next.js CONTENT_DIR

Quick Start — Adding Content

::: tip New doc in 4 steps

  1. Create your .md file in the appropriate canonical directory:

    # Registry-specific page:
    touch docs/docs-site/services/npm/my-topic.md
    
    # Cross-cutting guide:
    touch docs/docs-site/services/registries/guides/my-guide.md
  2. Write content with at least title and description in the frontmatter:

    ---
    title: My Topic
    description: What this covers
    ---
    
    # My Topic
    
  3. Sync to generate service-specific entries:

    task sync
  4. Commit the canonical file and the regenerated .service-specific/ entries:

    git add docs/docs-site/
    git commit -m "docs: add my-topic"

    :::

Content changes are picked up at runtime — no container rebuild required.


The Two Rendered Sites

Both sites serve the same content from the same canonical source. The only differences are in how they render and present it.

VitePress

  • Static site generator: builds all pages at container startup
  • Sidebar navigation configured in docs-site-vitepress/.vitepress/config.mts
  • Container: registry-docs-vitepress

Next.js

  • App Router with dynamic MDX loading: renders pages at request time
  • Sidebar and navigation generated from the services/ directory structure
  • Container: registry-docs-nextjs

The docs-sync.yml include patterns and extension_override handle the only technical difference between the two content roots:

services:
  vitepress:
    include: ["**/*.md"]      # all canonical Markdown files

  nextjs:
    include: ["services/**/*.md"]
    extension_override: ".mdx" # Next.js MDX loader requires .mdx extension

Further Reading

TopicDocument
Contributing and editing docsDeveloper Guide
Operating and maintaining servicesDevOps Guide
Content conventions and styleTechnical Writer Guide
Directory structure referenceContent Structure Guide