Skip to content
Khaled.dev
1 min read

Using Notion as a Build-Time CMS for a Static Site

How I wire Notion databases into a Next.js static export at build time — with a graceful fallback to bundled samples when credentials are missing.

I write in Notion. I deploy a static site. These two facts are in tension, and the resolution is a build-time pipeline that fetches Notion, converts pages to MDX, and caches the result so the runtime never touches Notion at all.

Why build-time, not runtime

A static export has no server. There's nowhere to call the Notion API from at request time, and I don't want one — static is fast, cheap, and durable. So the fetch happens once, during next build, and the output is committed as plain files.

The fallback rule

The single most important design constraint:

The site must build even with zero credentials.

If NOTION_TOKEN is absent, the pipeline logs a friendly message and the loader reads bundled sample MDX instead. Local dev, forks, and CI without secrets all just work.

# No env? No problem.
$ node scripts/fetch-content.mjs
skipping Notion (no creds) — using bundled samples
wrote public/search-index.json (9 records)

The shape of the loader

export async function getAllContent(type: ContentType): Promise<ContentDoc[]> {
  const dir = cacheDirExists(type) ? cacheDir(type) : bundledDir(type);
  const files = await readMdx(dir);
  return files.map(toContentDoc);
}

Cache-or-bundle is the only branch. Everything downstream is identical.

Lessons

  • Convert once at build, serve forever as static files.
  • Always ship sample content so the build is never blocked.
  • Keep the runtime ignorant of where content came from.
#nextjs#notion#content#static-site
ShareLinkedInX

Comments

Comments are enabled once giscus is configured.