A JSON-file blog that still scales
You don't need a database to publish a blog. You need a storage layer with a clean seam — so you can add one later without touching everything else.
Most blogs don't need a database on day one. They need fast pages, an editor that doesn't fight the author, and URLs that don't break. A folder of JSON files can satisfy all three — as long as nothing outside one module knows that's how it's implemented.
One seam, not zero
The trick isn't avoiding structure — it's choosing where the structure lives. Every page and Server Action in this project imports from lib/blog/queries.ts and lib/blog/actions.ts. Neither of those files touches the filesystem directly; they call into lib/blog/store.ts, which is the only module that knows content lives in content/blogs/*.json.
export async function getPostById(id: string): Promise<BlogPost | null> {
return readJson(path.join(BLOG_DIR, `${id}.json`), null);
}Move to Postgres later, and this is the only file that changes. Everything above it — pages, actions, the admin UI — keeps working, because it never knew where the data actually lived.
What this trades away
- No concurrent-write safety — fine for a single admin, not for a newsroom.
- Reads walk every file in the posts directory, which is instant at hundreds of posts and worth revisiting at tens of thousands.
- No filesystem writes on read-only hosting — plan your deploy target accordingly.
For a blog run by one or two people, publishing a few times a week, those trade-offs are the right ones. The moment they aren't, there's exactly one file to rewrite.