InfrastructureMay 2026Updated: 05/08/2026

How I Built This Blog — And How You Can Clone It in 10 Minutes

The complete stack behind this site: Next.js 16, Supabase, Vercel, and a one-file config. Fork it, fill in your details, deploy.

Blog deployment architecture

Every developer blog starts the same way: you spend a weekend building the blog instead of writing. I did the same thing — but I also turned the result into an open-source template anyone can use.

This guide walks you through every step — from zero to a fully deployed blog with an admin dashboard. No steps skipped, no assumptions. If you can copy-paste terminal commands, you can do this.

Repository: github.com/eliranbarhum/next-blog-starter


What You'll End Up With

Before we start, here's what the finished product includes:

  • A homepage with animated hero section, latest articles, and an about section
  • An insights archive with category filtering
  • Individual article pages with full markdown rendering — tables, code blocks, images, blockquotes
  • An admin dashboard at /admin where you write, edit, and publish articles from the browser
  • A comment system with honeypot spam protection
  • Dark/light mode that remembers your preference
  • Full SEO — JSON-LD schemas, Open Graph tags, Twitter cards, automatic sitemap
  • On-demand revalidation — changes in the admin dashboard go live instantly

Total cost to run: $0/month on the free tiers of Supabase and Vercel.


The Stack

LayerChoiceWhy
FrameworkNext.js 16 (App Router)Server components, static generation, API routes — all in one
DatabaseSupabase (PostgreSQL)Free tier is generous, built-in Row Level Security, instant API
HostingVercelZero-config deploys from GitHub, edge network, free analytics
StylingTailwind CSS 4Utility-first, dark mode via CSS custom properties
AuthJWT + bcryptSimple session cookies — no third-party auth dependency
StorageSupabase StorageArticle images served from a public bucket

No over-engineering. No headless CMS with a $50/month plan. No complex build pipeline. Just a database, a framework, and a deploy button.


Step 1: Get the Code

Open your terminal and run:

git clone https://github.com/eliranbarhum/next-blog-starter.git
cd next-blog-starter
npm install

This installs all dependencies. It takes about 30 seconds.


Step 2: Create a Supabase Project

Supabase is the database that stores your articles, comments, and admin login.

  1. Go to supabase.com and sign up (free)
  2. Click New Project
  3. Choose a name (e.g., my-blog), set a database password (save it somewhere — you won't use it directly, but Supabase needs it), and pick a region close to your audience
  4. Wait about 2 minutes for the project to spin up

Run the Setup Script

Once your project is ready:

  1. In the Supabase dashboard, click SQL Editor in the left sidebar
  2. Click New Query
  3. Open the file supabase/setup.sql from the repo you cloned
  4. Copy the entire contents and paste it into the SQL Editor

Before you run it, scroll to the bottom of the script. You'll see:

INSERT INTO admin_users (email, password_hash)
VALUES (
  '[email protected]',
  crypt('ChangeThisPassword!', gen_salt('bf'))
);

Change these two values:

  • Replace [email protected] with your real email
  • Replace ChangeThisPassword! with a strong password

This is your admin login for the dashboard. Remember these credentials.

  1. Click Run (or press Cmd+Enter)

You should see "Success. No rows returned" — that means everything worked. The script creates three tables (articles, comments, admin_users), sets up security policies, and creates a storage bucket for images.

Get Your API Keys

Still in the Supabase dashboard:

  1. Click Settings (gear icon) in the left sidebar
  2. Click API under Configuration
  3. You'll see two sections:

Project URL — Copy this. It looks like: https://abcdefghijk.supabase.co

Project API keys:

  • anon public — Copy this. This is your public key.
  • service_role secret — Click "Reveal" and copy it. Keep this secret. It bypasses all security policies.

Save these three values — you'll need them in the next step.


Step 3: Environment Variables

In the project folder, copy the example file:

cp .env.example .env.local

Open .env.local in any text editor and fill in the values:

NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...your-anon-key
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIs...your-service-role-key
ADMIN_JWT_SECRET=pick-any-random-string-like-this-one-abc123xyz

The ADMIN_JWT_SECRET can be any random string. It's used to sign login cookies. Something like my-blog-secret-2026-xyz works fine.


Step 4: Personalize Your Site

Open site.config.ts in your editor. This is the only file you need to customize. Everything else is automatic.

const siteConfig = {
  name: 'Your Name',
  siteUrl: 'https://yourdomain.com',
  siteName: 'YourSite',
  siteTagline: 'Your tagline here',
  profileImage: '/profile.jpg',
  hero: {
    badge: 'Your badge text here',
    headline: 'Your Main Headline ',
    headlineAccent: 'Goes Here.',
    subtext: 'A short paragraph about yourself.',
  },
  about: {
    shortBio: 'Your role — one sentence.',
    paragraphs: [
      'Your story, paragraph one.',
      'What you do today, paragraph two.',
      'What this site is about, paragraph three.',
    ],
    focusAreas: ['Topic 1', 'Topic 2', 'Topic 3'],
    // ... and more
  },
  social: {
    linkedin: 'https://linkedin.com/in/yourprofile',
    twitter: '',
    github: '',
  },
};

Profile image: Drop your photo into the public/ folder as profile.jpg (or any name — just update profileImage in the config to match).

When you change this file and deploy, every page title, meta tag, JSON-LD schema, footer copyright, and navigation logo updates automatically.


Step 5: Test Locally

npm run dev

Open http://localhost:3000 to see your blog. It will show "No articles published yet" — that's correct, you haven't written any yet.

Open http://localhost:3000/admin and log in with the email and password you set in the SQL script. You should see an empty article list with a "+ New Article" button.

Try creating a test article:

  1. Click + New Article
  2. Type a title — the slug auto-generates
  3. Write some text in markdown
  4. Click Publish
  5. Go back to http://localhost:3000 — your article should appear

If all of this works, you're ready to deploy.


Step 6: Deploy to Vercel

Push to GitHub

If you don't have a GitHub repo yet, create one:

  1. Go to github.com/new
  2. Name it (e.g., my-blog)
  3. Keep it private or public — your choice
  4. Don't initialize with README (you already have one)

Then push:

git remote set-url origin https://github.com/YOUR_USERNAME/my-blog.git
git add -A
git commit -m "My blog"
git push -u origin main

Import to Vercel

  1. Go to vercel.com/new
  2. Click Import next to your repo
  3. Before clicking Deploy, expand Environment Variables
  4. Add all four variables from your .env.local:
    • NEXT_PUBLIC_SUPABASE_URL
    • NEXT_PUBLIC_SUPABASE_ANON_KEY
    • SUPABASE_SERVICE_ROLE_KEY
    • ADMIN_JWT_SECRET
  5. Click Deploy

The build takes about 1-2 minutes. When it's done, Vercel gives you a URL like my-blog-abc.vercel.app.

Custom Domain (Optional)

In Vercel:

  1. Go to your project → Settings → Domains
  2. Add your domain (e.g., myblog.com)
  3. Vercel will show you DNS records to add at your registrar
  4. Once DNS propagates (usually a few minutes), HTTPS is automatic

Using the Admin Dashboard

Your admin dashboard lives at yourdomain.com/admin. Here's how to use it day-to-day:

Writing Articles

  1. Log in at /admin
  2. Click + New Article
  3. Type your title — the URL slug auto-generates (you can edit it)
  4. Choose a category from the dropdown
  5. Write your article in the markdown editor
  6. Add an excerpt in the sidebar — this shows in article previews and SEO
  7. Use Preview to see how it'll look
  8. Save Draft to save without publishing, or Publish to go live

Markdown Tips

The editor supports full markdown:

  • ## Heading for section titles
  • **bold** and *italic*
  • [link text](url) for links
  • ![alt text](url) for images
  • `inline code` for code
  • Tables, blockquotes, ordered and unordered lists

Uploading Images

Click Upload Image below the editor. Select a file (JPEG, PNG, GIF, WebP, SVG — max 5MB). The image uploads to Supabase Storage and a markdown image tag is inserted at your cursor position.

Managing Articles

From the dashboard at /admin:

  • Click an article title to edit it
  • Click the status badge (published/draft) to toggle visibility
  • Click View to see the live article
  • Click Delete to remove it permanently
  • Cmd+S saves while editing — you don't need to click a button

Changes Go Live Instantly

When you save or publish, the system triggers on-demand revalidation. The live site updates within seconds — no need to redeploy or wait for a build.


Architecture: How It Works Under the Hood

Public Pages

The homepage, insights listing, and article pages are all React Server Components. They fetch data from Supabase at request time, with a 60-second revalidation cache as a safety net. No client-side JavaScript is needed to render articles.

Article pages also use generateStaticParams for build-time pre-rendering, making them extremely fast on first load.

Authentication Flow

  1. Admin submits email + password to /api/admin/login
  2. Server queries admin_users table using the service role key (bypasses RLS)
  3. Password is verified with bcryptjs against the stored hash
  4. On success, a JWT is created with jose and set as an httpOnly cookie
  5. All admin API routes call getSession() which verifies the JWT
  6. The cookie expires after 30 days

Row Level Security (RLS)

Supabase enforces access rules at the database level:

  • Articles: Public users can only SELECT where status = 'published'. The service role key has full CRUD access.
  • Comments: Public users can SELECT and INSERT. No one can UPDATE or DELETE via the API.
  • Admin users: Only the service role key can access this table.

This means even if someone finds your anon key (it's in the client-side code), they can only read published articles and post comments. They can't access drafts, delete content, or read admin credentials.

On-Demand Revalidation

When the admin dashboard saves or publishes an article, it calls /api/admin/revalidate with the article slug. This endpoint uses Next.js revalidatePath() to clear the cache for:

  • The specific article page (/insights/[slug])
  • The insights listing page (/insights)
  • The homepage (/)

As a fallback, all pages set export const revalidate = 60 so they refresh every minute even without an explicit trigger.


Customizing Beyond the Config

Colors and Theme

Edit the CSS custom properties in app/globals.css:

:root {
  --accent: #2563eb;        /* Primary brand color */
  --background: #fafafa;    /* Page background */
  --foreground: #0a0a0a;    /* Text color */
  --surface: #ffffff;       /* Card background */
  --border: rgba(0,0,0,0.07);
}

.dark {
  --accent: #3b82f6;
  --background: #09090b;
  --foreground: #fafafa;
  /* ... */
}

Change --accent to match your brand. Everything else adapts automatically.

Article Categories

Edit the CATEGORIES array in components/admin/ArticleEditor.tsx:

const CATEGORIES = ['Infrastructure', 'AI Strategy', 'Kubernetes', 'Cloud', 'Security', 'Insight'];

Replace these with your own topics.

Disabling Comments

Remove the <Comments /> component from app/insights/[slug]/page.tsx. That's it — no other changes needed.

Adding Pages

Create a new file in app/ following Next.js App Router conventions. For example, app/projects/page.tsx creates a /projects page. Add a link in the navigation inside app/page.tsx.


Using Claude to Improve Your Blog

The repo includes a CLAUDE.md file that gives Claude full context about the project architecture, file structure, and conventions. This means you can open the project in Claude Code or Cowork and ask things like:

  • "Add a newsletter signup form to the homepage"
  • "Create a /projects page with a grid layout"
  • "Add reading time estimation to articles"
  • "Change the accent color to green and adjust the dark mode palette"
  • "Add an RSS feed"
  • "Improve the SEO metadata for better Google ranking"

Claude will understand the codebase, follow the existing patterns, and make changes that work with the rest of the site.


Troubleshooting

"No articles published yet" on the live site

This is normal if you haven't written any articles. Go to /admin, create one, and publish it.

Admin login doesn't work

Double-check that you changed the email and password in setup.sql before running it. If you already ran it with the defaults, you can update the password in Supabase:

UPDATE admin_users
SET password_hash = crypt('YourNewPassword', gen_salt('bf'))
WHERE email = '[email protected]';

Changes in admin don't appear on the site

The revalidation system handles this automatically. If it's still stale after 60 seconds, check that your SUPABASE_SERVICE_ROLE_KEY is set correctly in Vercel's environment variables. Redeploy after fixing env vars.

Build fails on Vercel

Make sure all four environment variables are set in Vercel → Settings → Environment Variables. The most common issue is a missing SUPABASE_SERVICE_ROLE_KEY.

Images don't upload

Check that the article-images storage bucket exists in Supabase (Storage → Buckets). The setup script creates it, but if you skipped that part, create a bucket called article-images and set it to Public.


Final Thought

The best blog is the one you actually write on. If the infrastructure gets in the way, you won't publish. This stack is designed to disappear — so you can focus on the content.

Fork it. Make it yours. Start writing.

Found this useful? Share it.Share on LinkedIn

Discussion

No comments yet. Be the first to start the discussion.

Join the conversation