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.

Priya Nair1 min read

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

  1. No concurrent-write safety — fine for a single admin, not for a newsroom.
  2. Reads walk every file in the posts directory, which is instant at hundreds of posts and worth revisiting at tens of thousands.
  3. 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.

#nextjs#architecture#static-sites