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.
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
/adminwhere 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
| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 16 (App Router) | Server components, static generation, API routes — all in one |
| Database | Supabase (PostgreSQL) | Free tier is generous, built-in Row Level Security, instant API |
| Hosting | Vercel | Zero-config deploys from GitHub, edge network, free analytics |
| Styling | Tailwind CSS 4 | Utility-first, dark mode via CSS custom properties |
| Auth | JWT + bcrypt | Simple session cookies — no third-party auth dependency |
| Storage | Supabase Storage | Article 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.
- Go to supabase.com↗ and sign up (free)
- Click New Project
- 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 - Wait about 2 minutes for the project to spin up
Run the Setup Script
Once your project is ready:
- In the Supabase dashboard, click SQL Editor in the left sidebar
- Click New Query
- Open the file
supabase/setup.sqlfrom the repo you cloned - 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.
- 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:
- Click Settings (gear icon) in the left sidebar
- Click API under Configuration
- You'll see two sections:
Project URL — Copy this. It looks like: https://abcdefghijk.supabase.co
Project API keys:
anonpublic— Copy this. This is your public key.service_rolesecret— 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:
- Click + New Article
- Type a title — the slug auto-generates
- Write some text in markdown
- Click Publish
- 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:
- Go to github.com/new↗
- Name it (e.g.,
my-blog) - Keep it private or public — your choice
- 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
- Go to vercel.com/new↗
- Click Import next to your repo
- Before clicking Deploy, expand Environment Variables
- Add all four variables from your
.env.local:NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYADMIN_JWT_SECRET
- 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:
- Go to your project → Settings → Domains
- Add your domain (e.g.,
myblog.com) - Vercel will show you DNS records to add at your registrar
- 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
- Log in at
/admin - Click + New Article
- Type your title — the URL slug auto-generates (you can edit it)
- Choose a category from the dropdown
- Write your article in the markdown editor
- Add an excerpt in the sidebar — this shows in article previews and SEO
- Use Preview to see how it'll look
- Save Draft to save without publishing, or Publish to go live
Markdown Tips
The editor supports full markdown:
## Headingfor section titles**bold**and*italic*[link text](url)for linksfor 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
- Admin submits email + password to
/api/admin/login - Server queries
admin_userstable using the service role key (bypasses RLS) - Password is verified with
bcryptjsagainst the stored hash - On success, a JWT is created with
joseand set as an httpOnly cookie - All admin API routes call
getSession()which verifies the JWT - 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.
Discussion
No comments yet. Be the first to start the discussion.