## Summary
- Switch the docs catch-all route to full static generation and prebuild
both latest and versioned docs paths.
- Prebuild OG images for all docs pages, make sitemap and LLM export
static, and remove nondeterministic sitemap timestamps.
- Reduce build-time overhead by memoizing docs sidebar trees and
skipping processed markdown generation for versioned docs.
## Testing
- `pnpm nx run @zitadel/docs:build`
- `pnpm nx run @zitadel/docs:lint`
- `pnpm nx run @zitadel/docs:check-types`
- Verified the prerender manifest contains 8,816 prerendered routes,
3,293 versioned docs routes, 4,406 OG routes, and zero revalidating docs
routes.
# Which Problems Are Solved
The Vercel `docs` project generated ~64M ISR writes over 30 days (99.5%
of ISR writes across all projects, ~\$258/month).
Root causes in the Next.js 16 docs app:
- `apps/docs/app/[[...slug]]/page.tsx` had `dynamicParams = true` +
`revalidate = 3600`. Bot traffic hitting unknown URLs (`/docs/wp-admin`,
`/docs/.env`, fuzzed paths) got rendered via \`notFound()\`, and the 404
response was cached as an ISR entry — 1 write per unique bad URL. Known
pages were also rewritten hourly for no reason since content only
changes on deploy.
- `apps/docs/app/og/docs/[...slug]/route.tsx` had `revalidate = false` +
empty `generateStaticParams()` + implicit `dynamicParams = true`. Every
unique OG URL (including bot probes) was cached forever — writes
accumulated permanently.
# How the Problems Are Solved
Switch the docs routes to pure SSG (content is static and only changes
on deploy, so ISR provides no value):
- `app/[[...slug]]/page.tsx`: `dynamicParams = false`, `revalidate =
false`, `dynamic = 'force-static'`. Unknown URLs now return a static 404
at the CDN — no function invocation, no ISR write. All 390 pages from
`source.generateParams()` are still pre-rendered.
- `app/og/docs/[...slug]/route.tsx`: `generateStaticParams()` now
returns all 390 pages via the existing `getPageImage(page).segments`
helper, so every OG image is pre-built as a static asset. `dynamicParams
= false` + `dynamic = 'force-static'` locks it down.
- `app/llms-full.txt/route.ts`: added `dynamic = 'force-static'` as a
safety net (already `revalidate = false`, single URL).
The tradeoff is longer CI builds (~40s–2min for 390 OG image
generations, paid on every preview deploy) in exchange for eliminating
~\$258/month in ISR writes plus associated function invocations and CPU
time.
# Additional Changes
None.
# Additional Context
- No changes to `next.config.mjs`, `vercel.json`, or redirects.
- Existing `apps/docs/redirects.json` (3,261 entries) covers legacy URLs
so `dynamicParams = false` won't 404 moved pages linked from elsewhere.
- Versioned routes: `content/versions.json` and `v*/` folders don't
exist yet. When versioning is activated, `generateStaticParams()` in
both files must also include `versionSource.generateParams()` —
otherwise versioned URLs will 404 under `dynamicParams = false`.
## Test plan
- [ ] CI build succeeds (expect modest build-time increase for OG
pre-generation)
- [ ] Inspect `apps/docs/.next/prerender-manifest.json` — all 390 doc
routes + 390 OG routes listed with `initialRevalidateSeconds: false`
- [ ] Local smoke: `/docs` → 200, `/docs/wp-admin` → 404 (static, no
function), `/docs/og/docs/guides/start/image.png` → PNG,
`/docs/og/docs/bogus/image.png` → 404
- [ ] Post-deploy: Vercel **ISR Writes** metric drops to near-zero
within 24h
- [ ] Post-deploy: Vercel **Function Invocations** for `/og/docs/*` drop
to zero
- [ ] Verify no legitimate docs pages 404 (cross-check logs against
`apps/docs/app/sitemap.ts`)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
## Todos for release
- [x] Configure Env in docs project on vercel
- [x] Configure Root Path in the docs project on vercel
- [ ] Remove old CSP https://github.com/zitadel/website/pull/1592
## What we did
This pull request migrates the project documentation from the old
`docs/` directory to the new `apps/docs/` directory, introduces a new
documentation system built with Next.js and Fumadocs, and updates all
relevant references, configuration files, and documentation to reflect
this change. It also adds new configuration and ignore files for the new
documentation app, updates CI and linting to exclude the new docs from
certain checks, and revises the contributing guidelines accordingly.
**Documentation System Migration and New Docs App**
* Migrated all documentation from `docs/` to `apps/docs/`, and updated
all references in `README.md`, `CONTRIBUTING.md`, and other files to
point to the new location.
[[1]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L585-L640)
[[2]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L660-R607)
[[3]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L740-R687)
[[4]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L2-R3)
[[5]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L30-R30)
* Added a new Next.js/Fumadocs-based documentation app under
`apps/docs/`, including core app files, layouts, routing, search API,
and a comprehensive `README.md` with development and contribution
instructions.
[[1]](diffhunk://#diff-5a1b07344a2c1b4d3f37b23ff1388b62cd9f57dea3c1cd23d8a0412b7602b132R1-R74)
[[2]](diffhunk://#diff-462b9ad1eabbb7d1c29bb9c36e4931eb180fd7190900e6d2babf8f4d66ad1c28R1-R39)
[[3]](diffhunk://#diff-e16ae25660ded787b10ac35dea96d5ecaacf895dae0afc8a9bd4382dc79a8c87R1-R7)
[apps/docs/app/[[...slug]]/layout.tsxR1-R81](diffhunk://#diff-59e08acde4e805b7aeccef1dcf98f1d71dfc550777e6b9402085cee0e9fa4e0aR1-R81),
[apps/docs/app/[[...slug]]/page.tsxR1-R79](diffhunk://#diff-e5df3f80d0fa01e12d63d81f29c57a9d14e846c78fd3beabb2ef768e38fd9580R1-R79),
[[4]](diffhunk://#diff-389b34918e040cacaa87cd7201ffa462cc2b0b716736f537e3d3c660ac69353bR1-R7)
[[5]](diffhunk://#diff-d3b03416d1c457b19f1c27b26f2db741412df841b875c26ccadc36e4522247f4R1-R29)
[[6]](diffhunk://#diff-c8fb8339570a5305809be7c618e14705fd86390dc278e6ce8ba224a7bc8b0c3cR1-R25)
**Configuration and Tooling Updates**
* Updated `.github/workflows/codeql.yml`, `.golangci.yaml`, and
`.github/dependabot.yml` to properly handle the new docs app: excluded
`apps/docs` from certain checks, added npm dependency updates for the
docs app, and excluded generated content.
[[1]](diffhunk://#diff-12783128521e452af0cfac94b99b8d250413c516ec71fe6d97dbea666ff7ba27L8-R14)
[[2]](diffhunk://#diff-9917ddc9f1c3304218f7269265b746d997c5c0615478177b5fceecd33ef47cb5R5-R6)
[[3]](diffhunk://#diff-9917ddc9f1c3304218f7269265b746d997c5c0615478177b5fceecd33ef47cb5R126-R129)
[[4]](diffhunk://#diff-dd4fbda47e51f1e35defb9275a9cd9c212ecde0b870cba89ddaaae65c5f3cd28R89-R106)
* Updated `.devcontainer/devcontainer.json` to use the latest Go 1.25.3
version for consistency.
**Licensing and Miscellaneous**
* Added `apps/docs/` to the list of licensed directories in
`LICENSING.md`.
These changes ensure the documentation is now maintained in a modern,
scalable system and all project tooling is updated to support the new
structure.
---------
Co-authored-by: Federico Coppede <[email protected]>