Back to Articles
v0vercelsupabasecmsnextjsbuild-in-public

Adding a Lightweight CMS to bilalh.dev with v0 + Supabase

Phase 2 of my site: I kept articles as markdown-in-code to ship fast, then upgraded to a lightweight CMS (admin-only) using v0 + Supabase in a few clicks.

Bilal HamdaniehJanuary 18, 20266 min read

When I first built bilalh.dev, I intentionally kept the blog system as markdown-in-code.

Not because that’s the “ideal” CMS — but because it’s the fastest way to ship a clean writing setup without turning the project into a full product.

Then came Phase 2 (the plan from day one): a real CMS so I can publish without opening my IDE every time.

This post is how I set it up using v0 by Vercel + Supabase, and how v0 made the process feel… unfairly fast.

The goal

I wanted something simple, but real:

  • Admin-only access (no user accounts, no over-engineering)
  • A hidden admin URL (not linked anywhere publicly)
  • Password login stored in env vars
  • Server-side auth (no secrets exposed in the client)
  • Articles stored in a database (still markdown)
  • A basic editor + rendered markdown preview
Basically: “WordPress features” minus 99% of the overhead.

What I chose for storage (and why)

I went with Supabase.

Why it’s a great fit for a small portfolio site:

  • fast setup
  • free tier is usually enough for low-traffic personal sites
  • Postgres underneath (simple + powerful)
  • easy to deploy + connect to Next.js
Also: v0 already knows the common Supabase patterns, so it can generate the scaffolding cleanly.

Step-by-step: building the CMS with v0

Step 1: Tell v0 exactly what you want (and what you don’t want)

I started by giving v0 a clear spec.

Here’s a real prompt (slightly cleaned up for readability):

> We have markdown-based articles, but they are written in code.
> I want to build a very simple but comprehensive CMS with a proper admin page.
>
> - super light login page for the admin (me)
> - keep the admin URL hidden from all pages (I must type it directly)
> - password stored in env variables
> - login must use server-side fetch to avoid exposing keys to the client
> - I can add articles in markdown format with inputs for slug/title/etc
> - ideally preview the rendered markdown
> - suggest and use a data storage of your liking

I like prompts like this because they:

  • lock in constraints early (hidden route, env password, server-side auth)

  • leave implementation choices open (storage + structure)

  • keep the scope intentionally “small but complete”


Step 2: v0 suggested Supabase and set it up fast

v0 proposed using Supabase for storage, which matched what I wanted.

Then it walked through the connection step, and the flow felt like:

  • pick Supabase
  • connect project
  • generate client utilities
  • create a migration SQL file
At this point, I didn’t even write custom DB boilerplate manually — v0 produced the setup and wired it into the project structure.

Step 3: The database table (SQL migration)

v0 generated a SQL script for the articles table and asked me to run it.

This was the exact message:

> Please execute scripts/001_create_articles_table.sql

That’s the part that felt so convenient:
I didn’t have to hand-write a schema from scratch in a separate tool, copy/paste it, and hope I didn’t forget a column.

v0 basically turned it into: “Here’s your migration. Run it.”

A typical schema for this CMS includes fields like:

  • slug (unique)
  • title
  • excerpt
  • content (markdown)
  • tags (array)
  • cover_image
  • published (boolean)
  • timestamps

Step 4: Admin login (env password + server-side validation)

Instead of building full auth + users, I wanted the simplest secure setup:

  • a single ADMIN_PASSWORD stored in environment variables
  • login form submits to a server route/action
  • server validates password, then sets an HTTP-only cookie
  • cookie gatekeeps the admin pages
Here’s the kind of prompt I used to keep it aligned:

> Build the admin login with server-side auth using env password.
> Persist session via HTTP-only cookies.
> No keys in the client.

This approach is perfect for a personal site because:

  • you get a real “admin-only” area

  • you avoid shipping auth complexity

  • it’s still safe enough for the use-case (assuming you use a strong password)


Step 5: Admin UI (CRUD + markdown preview)

After login, v0 generated:

  • a dashboard listing articles
  • create/edit/delete flows
  • an editor with markdown input
  • a preview mode that renders markdown
I also asked for something I care about a lot:

> Make the UI feel lightweight and consistent with the site’s design.

So the CMS didn’t look like a separate “admin tool” — it looks like part of the site.

Step 6: Hook the public blog back to the database

Finally, the blog pages that previously read from content/articles/ were updated to read from Supabase.

That meant:

  • getAllArticles() became async
  • pages became server components where needed
  • any client components that were calling sync functions had to be adjusted
There was even a tiny bug in the generated code (import/export mismatch), and v0 fixed it quickly once spotted.

That’s the real loop with these tools:
1) generate
2) validate
3) fix quickly
4) ship

What the CMS can do now

  • ✅ hidden admin URL (not linked anywhere)
  • ✅ env password login
  • ✅ server-side validation (no secrets on the client)
  • ✅ session via HTTP-only cookie
  • ✅ create/edit/delete posts
  • ✅ markdown editor + preview
  • ✅ Supabase DB storage
And the biggest win: I can now publish without touching the codebase.

What I learned

1) Shipping “simple first” was the right move

Markdown-in-code was the correct Phase 1. It helped me start writing immediately.

The CMS is Phase 2 — but I only built it once I had momentum.

2) v0 is not “magic” — it’s leverage

You still need to be precise about constraints (auth, hidden route, server-side flow).

But once you communicate well, the speed is ridiculous.

3) Supabase is perfect for low-traffic portfolios

If you just want:
  • posts stored somewhere reliable
  • a simple admin editor
  • no backend headaches
…it’s a sweet spot.

Next

Now that the CMS is in place, I’ll start posting more articles on bilalh.dev soon — without the friction of “open repo → edit file → commit → deploy” every time.


If you have questions or want to share what you built, feel free to connect with me on LinkedIn or check out my GitHub.
B

Written by Bilal Hamdanieh

AI Engineer specializing in Voice AI Agents and intelligent automation systems. Building the future of human-AI interaction at Marr Labs, based in Lebanon.