/blog, /docs and /changelog render content through a CMS client chosen by CMS_CLIENT:
keystatic (the value in apps/web/.env): Markdoc files in the repository.wordpress: posts from a WordPress site at WORDPRESS_API_URL.With Keystatic, content lives in apps/web/content/ (set by NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH=./content):
| Folder | Page |
|---|---|
posts/ | /blog/<file name> |
documentation/ | /docs/<folder>/<file name> |
changelog/ | /changelog/<file name> |
showcase/products.json | /showcase (read directly, not through the CMS) |
Every published post, doc page and changelog entry is added to the sitemap, with publishedAt as its last-modified date.
Each .mdoc file starts with front matter. The schema is in packages/cms/keystatic/src/keystatic.config.ts:
| Field | Used for |
|---|---|
title | Page title |
description | Subtitle, card text and meta description |
publishedAt | Date (YYYY-MM-DD). Blog and changelog are sorted by it, newest first |
status | Only published entries are shown. Also draft, review, pending |
order | Position in the docs sidebar |
label | Shorter name for the docs sidebar |
image | Cover image for posts, from public/site/images |
categories, tags | Lists of strings |
collapsible, collapsed | Docs only: make a sidebar section fold |
A folder with a file of the same name is a section: documentation/billing/billing.mdoc is the Billing page, and the other files in documentation/billing/ appear under it. Sections and pages are sorted by order. The /docs landing page shows one card per section.
The body is Markdoc, which reads like Markdown: headings, lists, links, code blocks, tables and images work. No custom Markdoc tags are registered; add your own in packages/cms/keystatic/src/custom-components.tsx.
The Keystatic editing UI is not mounted, so you edit the files directly (or let your agent do it). pnpm turbo gen keystatic adds the admin routes and the @keystatic/next dependency if you want an editor. Storage defaults to local files; NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND can be github or cloud with the related KEYSTATIC_* variables.
content/showcase/products.json is an array of products checked against apps/web/app/[locale]/(marketing)/showcase/_lib/showcase.schema.ts: slug, name, tagline, url, makerName, optional makerUrl, category, platforms, launchedAt, featured and an optional logo. The form on /showcase emails submissions to CONTACT_EMAIL. You review them and add the ones you accept to the file. An empty array shows a "no products yet" message.