Official documentation site for Jean.
This repo uses Fumadocs on TanStack Start with Bun, then exports the docs as a static site that is served from /docs.
- TanStack Start app powered by Bun
- Fumadocs content authored in MDX
- Static local search
- Static export for
/docs - Build-time OG image generation
- SEO metadata for docs pages
- Build-time
robots.txtandsitemap.xml - Global MDX components, including zoomable images and media cards
- Plausible pageview tracking
- nginx config for static serving and
/docsrouting - Docker image for deployment
- GitHub Actions for staging and production image builds
jean-docs/
├── src/
│ ├── .env.example
│ ├── content/docs/ # MDX docs content
│ ├── public/ # images, brand assets, manifest
│ ├── scripts/ # post-build generation
│ ├── src/ # app code, components, routes
│ ├── package.json
│ ├── source.config.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── .github/workflows/
├── Dockerfile
├── LICENSE
├── nginx.conf
└── README.md
Everything related to the docs app lives under src/. The repo root only keeps deployment and project-level files such as Dockerfile, nginx.conf, LICENSE, Git metadata, and this README.
- Bun
- Docker, if you want to test the production image locally
Install dependencies:
cd src
bun installStart the dev server:
bun run devOpen http://localhost:3000/docs
Build the static site:
cd src
bun run buildGenerated output is written to:
src/.output/public/
Important generated files:
src/.output/public/_shell.htmlsrc/.output/public/assets/*src/.output/public/docs-manifest.jsonsrc/.output/public/robots.txtsrc/.output/public/sitemap.xmlsrc/.output/public/og/*
The build also removes non-static Nitro server artifacts so the deploy output stays static-only.
Copy src/.env.example to src/.env and adjust the values for your environment.
cp src/.env.example src/.envCurrent variables:
# Public site URL used for canonical URLs, sitemap entries, and OG metadata
VITE_SITE_URL=https://example.com
# Plausible script URL loaded by the browser
VITE_PLAUSIBLE_SCRIPT_URL=https://plausible.example.com/js/script.js
# Domain recorded in Plausible analytics for these docs pageviews
VITE_PLAUSIBLE_DOMAIN=example.com
# Optional: only set this if events should go to a different host or base path
# VITE_PLAUSIBLE_API_HOST=https://plausible.example.comFor Jean production, the docs are expected to live under https://jean.build/docs, so Plausible should usually track jean.build/docs.
Build the image:
docker build -t jean-docs:latest .Run it locally:
docker run -p 8080:8080 jean-docs:latestOpen http://localhost:8080/docs
- Dependencies are installed with Bun inside the builder stage.
- The docs app is built inside
src/. - Only static output from
src/.output/publicis copied into nginx. - nginx serves the docs from
/docs.
The container listens on port 8080.
nginx.conf is intentionally small and only handles static serving concerns:
- serve the built docs from
/docs - serve hashed assets from
/docs/assets - serve OG images from
/docs/og - fall back to
/docs/_shell.htmlfor docs routes - do not redirect
/to/docs
That last point matters because the main Jean website lives at the root domain and the docs live only under /docs.
Docs content lives in:
src/content/docs/This is where the sidebar structure, page frontmatter, and MDX content live.
Reusable docs components are globally available through the MDX setup, so pages can use components like zoomable images and media cards without importing them manually on every page.
Search is configured to use local static search by default.
That means the docs build includes the data needed for local search without requiring an external search backend for the default experience.
The post-build script handles more than just cleanup. It also generates:
- Open Graph images for docs pages
robots.txtsitemap.xmldocs-manifest.json- copied public brand and image assets under the
/docspath
The relevant script lives at src/scripts/postbuild.ts.
Plausible is wired for pageview tracking through environment variables so the same codebase can be used for local, staging, and production environments.
Production and staging workflow defaults are:
- production domain:
jean.build/docs - staging domain:
next.jean.build/docs
The repo includes two build-and-publish workflows:
production-build.ymlstaging-build.yml
They build multi-arch Docker images for GHCR and then trigger deployment through Coolify webhooks.
Production:
VITE_PLAUSIBLE_SCRIPT_URLVITE_PLAUSIBLE_API_HOST(optional)COOLIFY_WEBHOOKCOOLIFY_TOKEN
Staging:
VITE_PLAUSIBLE_SCRIPT_URL_NEXTVITE_PLAUSIBLE_API_HOST_NEXT(optional)COOLIFY_WEBHOOK_NEXTCOOLIFY_TOKEN
# install dependencies
cd src && bun install
# start dev server
cd src && bun run dev
# production build
cd src && bun run build
# type check
cd src && bun run types:check
# build Docker image
docker build -t jean-docs:latest .
# run Docker image locally
docker run -p 8080:8080 jean-docs:latest