Every blog eventually hits the question: where should the content live?
The usual answers cluster into three camps:
- Markdown files in the repo. Fast, version-controlled, free. Requires a laptop, a git client, and comfort with Markdown. Not great for writing from a phone, or for non-technical contributors.
- Hosted headless CMS (Sanity, Contentful, Ghost). Lovely editor, scheduled publishing, media library. But the content lives in someone else’s database, syndication is awkward, and it’s another service to pay for and maintain.
- Database-backed + custom admin UI. Full control. Also weeks of work you could spend on actual product.
For this blog we wanted the editor experience of the middle option and the durability of the first. We already live in Notion for planning docs, so the cheap answer was obvious: treat Notion as the editing surface, but keep MDX in the repo as the source of truth.
The shape
One-way sync. Notion to repo, never the other direction. Edits made directly to the MDX files get overwritten on the next sync — the repo is the artifact, not the editing surface.
What the sync does
The script is under 300 lines of TypeScript, and the moving pieces are modest:
- Query the Notion data source for pages with
StatusinReadyorPublished - For each page: convert blocks to Markdown with
notion-to-md, generate frontmatter from the page properties, download any Notion-hosted images into the repo (Notion’s image URLs expire in an hour, so keeping the originals is not an option) - Reconcile: any slug in the repo that isn’t in the synced set gets removed — orphan cleanup is how “delete” works
- Commit, push, dispatch the Deploy workflow
No headless CMS, no new infra, no monthly bill. The only thing Notion contributes is the editor.
Trade-offs
The main one: hourly-at-best freshness. A Notion-triggered webhook is not a thing you can rely on for database updates, so we poll. In practice this is fine — blog posts are not Slack messages; a five-minute delay from “Status = Ready” to “live on the site” is irrelevant.
Second: images live in the repo. For a small blog this is fine; for a media-heavy one it would bloat the repo and should move to object storage. Easy to change later — the script’s image-download step is one function.
Third: round-tripping is one-way. If you prefer editing MDX in VS Code sometimes and in Notion other times, you’d want a bidirectional sync, which is a different beast. For a single author who picked Notion as the editor, the one-way model is simpler and safer.
When this makes sense
Teams already using Notion for docs. Single authors who want a browser-based editor but also want to keep their content and their deploy pipeline close to the code. Anyone who has rejected the operational cost of a headless CMS but still wants to write from a phone.
A thousand lines of infra you don’t need is a thousand lines of infra you don’t have to maintain.