DEV Community

kouliavtsev
kouliavtsev

Posted on

Founders & Indie Hackers: Stop Putting All Your MVP Content in a Database

I’m building OCR Trips — an obstacle course race trip planner that helps you:

  • Discover races
  • Plan trips
  • Budget flights, hotels, and race fees in one place

The “app” part (accounts and trip plans) runs on Supabase. Ow, yeah! 🏄 But for the MVP content, I didn’t want everything living in a database.

Users would manually add races they know. That was the original idea: a lightweight trip planner where you plug in your next OCR and we help with flights, hotels, and budget.

Then I thought: what if someone doesn’t have a race yet and needs help finding one?

So I expanded the site with Obstacle Course Race Calendar — a public directory where anyone can:

  • Browse obstacle course races by country and by city.
  • See nearby airports and rough travel ideas
  • Check typical weather for race dates
  • Filter by brand and distance etc.

My first thought:

“Easy. Add a races table in Supabase and build an admin UI.”

My second thought:

Do I really want to put stuff in supabase?

So instead of a races table, I did this:

content/races/
├── spartan/
│   └── 2026/
│       ├── paris.mdx
│       └── london.mdx
├── tough-mudder/
│   └── 2025/
│       └── atlanta.mdx
└── ... 200+ files
Enter fullscreen mode Exit fullscreen mode

Each file has frontmatter + MDX: brand, date, location, distances, coordinates, terrain, plus a description.

Velite reads those files at build time, validates them, and gives me a typed dataset I can import in Next.js.

What I avoided by not using Supabase for races

By not putting race data in the DB (yet), I skipped:

  • Writing migrations every time I tweak race fields
  • Building a custom admin panel just to edit content
  • Managing seed scripts for dev/staging/prod
  • Worrying about schema drift between environments
  • Dealing with runtime bugs like “DB not reachable” for pages that could be static

For an MVP, that’s all extra surface area to maintain.

Instead, my workflow is:

  1. Edit a .mdx file
  2. Commit
  3. Vercel rebuilds
  4. /races is up to date

Deployment = publishing.

How I solved it instead: MDX + Velite

Velite makes the flat-file approach feel surprisingly “database-like” — but without the DB.

At build time it:

  • Validates content against a Zod/TS schema
  • Generates TypeScript types
  • Outputs an array I can import and filter

In code, it looks roughly like this (simplified):

import { races } from "@/content/generated";

const frenchSpartan = races.filter(
  (race) =>
    race.brand === "spartan" &&
    race.country === "France"
);
Enter fullscreen mode Exit fullscreen mode

I still get:

  • Filters
  • Searching
  • Typed fields
  • Autocomplete in my editor

“But what if I outgrow this?”

That’s the nice part: starting in files isn’t a trap.

If /races ever needs:

  • Non-technical editors
  • Live updates without deploys
  • Super complex querying

…I can:

  1. Create a races table in Supabase
  2. Write a one-off script that reads the MDX files and inserts them into the DB
  3. Switch from import { races } to await db.query.races

The early choice (files) doesn’t block the later choice (database).

It just lets me ship faster now.

Conclusion: default to files for MVP content

If you’re a founder or indie hacker sketching out an MVP, it’s worth pausing before you:

  • model everything in SQL
  • build an admin UI
  • wire up forms to manage “content”

Ask:

  • Will users actually write to this?
  • Does it change constantly?
  • Do I need querying tools for it right now?

If the answer is “not really”, try this stack:

Files in Git + a schema layer (Velite or similar) + static generation.

You can always move it into a database later.

But you might not have to.

Top comments (0)