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.
Comments
Comments are enabled once giscus is configured.