What an MVP actually is
An MVP — Minimum Viable Product — is the smallest version of your idea that lets a real user accomplish the one core thing your product promises.
It is not:
- A polished design system
- An admin dashboard
- A settings page with 14 toggles
- A landing page with 9 sections and a pricing table you haven't validated
- A marketing site dressed up as a product
It is:
- One core flow, working end-to-end
- Real authentication
- Real data persistence
- Deployed on a real URL
- Used by at least 10 real humans who aren't you
This guide gives you a 30-day plan to ship one — using Next.js, because it gives you everything an MVP needs in one stack: frontend, backend, routing, API, and deployment.
The stack (pick once, stop debating)
For a Next.js MVP in 2026, pick this and move on:
- Framework: Next.js 16 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS v4 + shadcn/ui
- Database: PostgreSQL (Neon, Supabase, or Railway)
- ORM: Prisma
- Auth: NextAuth (Auth.js v5)
- File storage: UploadThing or Cloudflare R2
- Email: Resend
- Payments: Stripe (or Polar for simpler indie pricing)
- Hosting: Vercel (or Cloudflare Pages)
- Analytics: PostHog or Vercel Analytics
This stack is boring on purpose. Boring ships.
Week 1: scope, schema, scaffold
Days 1–2: write the spec
Open a blank doc. In one page or less, write:
- The one job your product does. ("Lets indie devs schedule social posts from a single Markdown file.")
- The one user it serves. ("Indie hackers and solo developers.")
- The single core flow. (Sign up → connect account → write post → schedule → posted)
- What's NOT in the MVP. (Team accounts. Analytics dashboard. Multiple workspaces. Cancel-anytime billing portal.)
The "not in scope" list is more important than the "in scope" list. Without it, you'll add features instead of shipping.
Day 3: design the data model
Before writing code, write your Prisma schema.
For most SaaS MVPs you need:
User(id, email, name, image, createdAt)Account&Session(NextAuth tables)Subscription(userId, stripeCustomerId, status, plan, currentPeriodEnd)- Your one or two domain models (the actual thing your app is about)
Keep it minimal. You'll add fields as you go. Adding fields is easy. Untangling a wrong schema in week 4 is painful.
Day 4: scaffold the project
pnpm create next-app@latest myapp --typescript --tailwind --app
cd myapp
pnpm dlx shadcn@latest init
pnpm dlx shadcn@latest add button input card form dialog dropdown-menu
pnpm add @prisma/client next-auth@beta @auth/prisma-adapter zod react-hook-form @hookform/resolvers
pnpm add -D prisma
pnpm dlx prisma init
Push your schema. Generate the client. Run a sanity migration.
pnpm dlx prisma migrate dev --name init
Days 5–7: auth + landing
By end of week 1, you should have:
- A deployed site (push to GitHub → connect to Vercel)
- A simple landing page (headline, sub-headline, CTA, one screenshot or mockup)
- Working email + Google OAuth sign-in
- A protected
/dashboardroute that says "Hello, {name}"
That's the foundation. No core feature yet — and that's correct.
If you're stuck on auth, follow the official Auth.js Next.js guide and don't deviate. Custom auth in week 1 is a trap.
Week 2: build the core flow
This is the week the product becomes a product. One flow, end-to-end.
Days 8–10: backend first
Build the server actions or API routes for your core flow before touching the UI.
For each, you should have:
- A Zod schema validating the input
- A Prisma query that does the database work
- An auth check at the top
- A typed return
- Error handling that returns useful messages
Example: a Server Action to create your domain object.
// app/items/actions.ts
'use server'
import { z } from 'zod'
import { auth } from '@/auth'
import { prisma } from '@/lib/db'
import { revalidatePath } from 'next/cache'
const createItemSchema = z.object({
title: z.string().min(1).max(120),
body: z.string().min(1).max(5000)
})
export async function createItem(input: unknown) {
const session = await auth()
if (!session?.user?.id) throw new Error('Unauthorized')
const data = createItemSchema.parse(input)
const item = await prisma.item.create({
data: { ...data, userId: session.user.id }
})
revalidatePath('/dashboard')
return item
}
Repeat for update, delete, list, get. That's usually it.
Days 11–14: build the UI
Now build the screens for the flow you just powered.
Stick to:
- Server Components by default
- Client Components only for interactive parts (forms, modals, drag/drop)
- shadcn/ui primitives, not custom-styled buttons from scratch
- Tailwind utilities, not custom CSS files
Don't design — assemble. shadcn/ui has 90% of the components an MVP needs. Use them, theme them lightly, ship.
By end of week 2 your core flow works end-to-end. You can sign up, do the core thing, see it persisted, and come back tomorrow to see it still there.
Week 3: payments, polish, and people
Days 15–17: payments
If your MVP is paid, integrate Stripe now — before sharing with users. It's easier to add billing while the app is simple.
The minimum Stripe setup:
- Create products and prices in Stripe Dashboard
- Add a Checkout Session API route (
/api/stripe/checkout) - Add a webhook handler (
/api/stripe/webhook) for these events:checkout.session.completedcustomer.subscription.updatedcustomer.subscription.deleted
- Update your
Subscriptionmodel from webhooks - Gate features by checking
subscription.status === 'active'
Use Stripe's test mode for everything in week 3. Switch to live mode only when you have a real customer ready to pay.
Days 18–19: empty states, errors, and edge cases
Every screen needs:
- An empty state — "You haven't created anything yet. [Create one]"
- A loading state — Skeletons or spinners (Next.js
loading.tsxfiles are great) - An error state — "Something went wrong. [Try again]"
- A success confirmation — toast, redirect, or inline message
These aren't polish. They're correctness. A product without empty states isn't usable by new users.
Day 20: legal essentials
Add these pages, even at MVP:
/privacy— what data you collect, how you use it, GDPR basics/terms— your terms of service/contact— how to reach you
You can generate first drafts with Termly or Iubenda and edit. Don't ship without them — Stripe, Google OAuth, and most app stores require them.
Day 21: invite 5 friends
Open it up to 5 trusted people. Watch them use it. Don't help. Don't explain.
Take notes:
- Where do they get confused?
- Where do they click expecting something that isn't there?
- What do they ignore that you thought was obvious?
You will be surprised. Everyone is.
Week 4: ship to real users
Days 22–24: fix what week 3 revealed
Fix the top 5 friction points from your friends' feedback. Resist the urge to add new features. Make the core flow obvious.
Add:
- A real onboarding (3 steps max)
- A help link or short FAQ
- A way for users to email you when stuck
Day 25: analytics and feedback
Install:
- PostHog or Vercel Analytics for usage data
- Crisp or Plain or just an
mailto:link for support - Sentry for error tracking
If you don't measure what users do, you'll build what you think they want — which is rarely what they actually want.
Day 26: write the landing page properly
Now you have a real product, write the landing page like you mean it.
Structure:
- Headline (the outcome, not the feature)
- Sub-headline (who it's for, in one line)
- Hero image or short video
- 3 benefits (with one screenshot each)
- Social proof (your 5 testers, with permission)
- Pricing
- FAQ
- CTA repeated 2–3 times
Avoid jargon. Talk about the user's problem, not your tech.
Days 27–28: prep launch
Decide where you'll launch:
- Indie Hackers — your founder story
- Product Hunt — schedule for a Tuesday or Wednesday launch
- Hacker News — only if there's a genuinely interesting technical or business story
- Twitter / X — daily build-in-public posts work over weeks, not days
- LinkedIn — for B2B
- Your existing email list (the most underrated launch channel)
- Niche communities where your users already hang out
Write a short, honest post for each. No hype. Tell the truth about what the product does, who it's for, and what you'd like feedback on.
Days 29–30: ship
Push live. Send the emails. Post in the communities. Then talk to every single person who tries it.
The first 100 users are not customers. They're co-builders. Treat them like one.
What "done" looks like at day 30
After 30 days you should have:
- A live, deployed, paid (or free-tier) Next.js app
- A real database with real user data
- Working authentication
- Working payments (if applicable)
- 10+ real users who aren't your mom
- A list of the top 5 things to build next, chosen by data, not by guesswork
That's an MVP. That's enough. The next 30 days are about iterating on what's actually used — not on what's still in your head.
Mistakes that turn 30 days into 6 months
- Designing instead of shipping. A boring, working product beats a beautiful, half-built one every time.
- Building features no one asked for. Talk to users between every sprint, not after the product is "done."
- Setting up a custom CI/CD pipeline in week 1. Vercel auto-deploys. That's enough until you have 1,000 users.
- Picking a trendy database you don't know. Use PostgreSQL. Migrate later if you must.
- Optimising before you have users. N+1 queries don't matter when you have 8 sign-ups.
- Trying to monetise before you have product-market fit. Get usage first, then pricing.
When to bring in help
If you hit a wall — auth complexity, payments edge cases, scaling issues, or just losing momentum — bring in someone who's shipped before.
If you reach that stage, get in touch. Otherwise: clock starts now. Day 1 begins tomorrow.
The best MVP is the one that exists. Yours is one focused month away.
