ZeroStarter

Content

Author docs and blog posts in MDX, with full-text search and draft/publish built in.

Everything you publish is MDX, and it lives in one place: web/next/content/. Under it, docs/ and blog/ are two Fumadocs collections that split on a single axis: who owns each page's metadata.

  • Docs declare their structure and metadata centrally, in web/next/docs.config.ts. You write only the body.
  • Blog posts carry their own metadata in frontmatter, and the listing builds itself from the posts.

Docs, the blog, and the private console docs are each an optional feature: turn one off and its routes 404 and it drops from the nav and search; docs and blog also drop from the sitemap and llms.txt (console docs are never in those by construction). The rest of this page assumes they are on.

Docs: the config is the source of truth

web/next/docs.config.ts is the single source for docs structure and metadata. Each collection maps to ordered sidebar groups, and every entry is keyed by the page's URL:

Manage: [
  {
    "/docs/manage/content": { title: "Content", label: "Content (Blog & Docs)", description: "…" },
  },
]

title and description are the page metadata; label overrides the sidebar text when it should differ from the title. A generator (.github/scripts/docs.ts) runs inside the web/next build and dev and, from that config:

  • writes each page's frontmatter (slug, title, description, and label when set), so you only author the body;
  • generates content/docs/meta.json: the reading order and prev/next;
  • scaffolds a stub MDX in dev for any page declared in the config but missing on disk;
  • fails a --strict build if the config and the files on disk have drifted.

So adding a page is three steps: add the entry to docs.config.ts, run the dev server (or bun .github/scripts/docs.ts) to scaffold the file, then write the body. Leave the frontmatter alone; it is regenerated on every build.

Don't edit meta.json

content/docs/meta.json is generated and git-ignored. The generator overwrites it, so hand edits are lost.

Blog: one file per post

A blog post is a single MDX file in web/next/content/blog/, and it owns its metadata in frontmatter. There is no list and no meta.json to maintain by hand; the /blog listing derives itself from the posts, newest first.

Two timestamps decide visibility: createdAt is bookkeeping; publishedAt is consent to publish. A post is public only when it is not a draft and publishedAt is set and not in the future.

---
title: "My Post Title"
description: "A short summary of the post."
createdAt: 2026-06-11
publishedAt: 2026-06-11
---

Set draft: true to hide a post regardless of its timestamps, and omit publishedAt for a draft with no release time yet. Timestamps are either YYYY-MM-DD (the start of that UTC day) or an ISO datetime with an offset ("2026-06-18T09:00:00+05:30") for a post that should go live at an exact time. Scheduled posts need no rebuild: every public blog surface re-evaluates publishing on render, so a post appears within about 60 seconds of its publishedAt as ISR regenerates. Post images are committed under web/next/public/marketing/blog/<slug>/images/ and referenced by absolute path.

Each post page renders its own publish and update dates and emits article Open Graph metadata from that same frontmatter. Running bun .github/scripts/docs.ts stamps a missing createdAt with today's date on a new post file and prints what it wrote, so validation always has one. Review and commit that line.

The publishing rule lives in one place (web/next/src/lib/blog-policy.ts) and the listing, sitemap, llms.txt, and OG routes all read it, so they can never disagree about what is public.

Full-text search is powered by Orama through Fumadocs, built by the route handler at web/next/src/app/api/search/route.ts. Press Cmd+K (or Ctrl+K) on any docs page to open it. That public index holds the docs collection only. The blog is not indexed, and the private console docs have an index of their own at /api/console/search, which answers 404 to anyone the console would not admit, so nothing in it leaks.

The private console docs

web/next/source.config.ts defines a third collection, consoleDocs (content/console/docs/), served only under the auth-gated /console area. Its source is deliberately never referenced by a public route, so it is excluded from the public search, the sitemap, and llms.txt by construction (its own search is gated like the pages are): a home for internal docs that ship with the repo but stay behind the console.

Next

  • Features: turn the docs, blog, or console docs on or off per fork.
  • SEO & Metadata: the OG images, sitemap, and robots that cover everything you publish.
  • llms.txt: how the docs collection becomes agent-readable context.