Full-Stack SaaS in 2 Hours: A Timed Build Log
The demo videos are not lying. A live, authenticated, read-write app really does take about 85 minutes on this stack. Here are my own stage-by-stage timings, the code, and the four things that broke after.
Table of contents
Yes — you can have a live, authenticated, read-write app on Next.js, Supabase and Vercel in under two hours. I have timed it. Across four platforms built on this exact stack the fast path is about 85 minutes: 10 to scaffold and deploy, 20 for schema, 15 for auth, 30 for CRUD screens, 10 for a real domain. That part of the demo is honest.
What the demo does not time is everything after minute 85. On the smallest thing I have shipped that tail was another six focused hours. On a client platform it was weeks. The videos are not lying — they are measuring the wrong thing.
The stage table
These are my own stopwatch numbers, not a benchmark. I looked for a controlled study of build times on this stack and could not find one; nobody has run that experiment. Treat the column as one developer's median across four builds, on a stack he had already used three times.
| # | Stage | My time | Cumulative | What exists when it is done |
|---|---|---|---|---|
| 1 | create-next-app, repo, first Vercel deploy | 10 min | 0:10 | A live URL on a preview domain |
| 2 | Supabase project, schema for four tables | 20 min | 0:30 | Tables. No policies. The anon key can do anything |
| 3 | Auth | 15 min | 0:45 | Login, logout, a session readable in a server component |
| 4 | CRUD screens over those tables | 30 min | 1:15 | You can create and edit rows from the browser |
| 5 | Custom domain, production deploy | 10 min | 1:25 | Real hostname, HTTPS, the thing you screenshot |
| 6 | Row Level Security written and tested | 3-4 h | — | Two users cannot read each other's rows |
| 7 | Loading, empty and error state per screen | ~20 min per screen | — | The app survives a new user and a dead network |
| 8 | Transactional email on a verified domain | half a day | — | Mail that does not land in spam |
| 9 | Caching and revalidation decisions | 1-2 h | — | You know what is stale, and for how long |
Rows 1 to 5 are the video. Rows 6 to 9 are the reason the video ends where it does.
On the v0 half of the question
The query this page answers names v0. I do not use it. I build with Claude Code against a repo, because the output I need is a diff I can read, not a component I have to reverse-engineer — the loop I actually run is in commit, push, deploy. Generation speed is not the variable that decides row 6. If v0 gets you through rows 1 to 4 faster than 75 minutes, take it. It will not touch rows 6 to 9.
What the first 85 minutes buys
Scaffold and deploy before you write anything
The first deploy should happen before there is anything to deploy. Ten minutes, on an empty app. The reason is that everything which can be wrong about hosting — DNS, build command, environment variables, the hostname you actually serve — is easier to find against a page that says nothing than against a half-built app where you cannot tell whether the failure is yours or the platform's.
I skipped this once and paid for it later: picking the wrong canonical hostname cost me three months of indexing.
The schema is fast because it is small
Four tables in twenty minutes is real, and it is real because four tables is a small schema you already understand. Do not read that number as "schema design takes twenty minutes." It is "typing a schema you have already decided takes twenty minutes."
Auth is 15 minutes because you are not writing it
This is where the stack genuinely earns the hype. Sessions, password reset, email confirmation, provider logins — none of it is yours. Fifteen minutes is honest, and it is the single biggest thing the stack gives you.
CRUD, and the decision hiding inside it
Thirty minutes of screens, and one choice inside them that matters more than the screens do. On this site a blog post is a server component that reads Supabase directly, prerendered at build and revalidated hourly:
// app/blog/[slug]/page.tsx
export const revalidate = 3600;
export async function generateStaticParams() {
const rows = await getBlogSlugs();
return rows.map(({ slug }) => ({ slug }));
}
revalidate and dynamicParams are Next.js route segment config (Next.js docs). Worth knowing before you copy that block: in Next.js 16, dynamic, dynamicParams, revalidate and fetchCache are removed when Cache Components is enabled. The caching model you learn from a tutorial recorded last year may not be the one your app is running.
The admin side of the same app is the opposite shape — client components behind an auth guard, no ISR, writes going straight through. I broke that split down in how I structure a Supabase and Vercel admin panel.
The tail nobody films
RLS is the first real wall
A fresh table in the public schema is not private. Supabase is explicit: "A table in an exposed schema without RLS is readable and writable by any role with a grant on it," and a new table in public "starts with every privilege already granted to all three roles," anon included (Supabase RLS guide).
Two details cost me an afternoon the first time I hit them.
The first: CREATE POLICY has no IF NOT EXISTS clause. The synopsis in the PostgreSQL documentation begins CREATE POLICY name ON table_name and offers AS, FOR, TO, USING and WITH CHECK — nothing else. So a migration you can safely re-run needs a guard:
alter table public.projects enable row level security;
do $$ begin
if not exists (
select 1 from pg_policies
where schemaname = 'public'
and tablename = 'projects'
and policyname = 'owner_reads_own'
) then
create policy owner_reads_own on public.projects
for select using (auth.uid() = owner_id);
end if;
end $$;
revoke insert, update, delete on public.projects from anon;
The second: that last line is not decoration. Policies do not take grants back. Supabase says it plainly — "A table protected only by policies still hands anon an insert path if you never revoke the grant."
The direction that saves you is the default. Postgres: "If row-level security is enabled for a table, but no applicable policies exist, a 'default deny' policy is assumed, so that no rows will be visible or updatable." Enable RLS on every table the moment you create it, and the failure mode becomes an empty screen you notice in the next minute instead of an open table you notice never.
Three states per screen, not one
The demo has data. Production has a user who has created nothing, a request that failed, and a phone on a train. That is three states per screen against the one the video showed, and it is where the twenty-minutes-per-screen row comes from. Ten screens is most of an afternoon.
Email has no ten-minute version
Transactional email means a provider, a verified sending domain, and DNS you do not control the propagation of. Resend generates DKIM and SPF as TXT records plus an MX or CNAME for the return path on a send. subdomain; their docs say a domain "will often verify within 15 minutes," while DNS changes can take "up to 72 hours to propagate globally" (Resend). Fifteen minutes is the good case. You cannot compress the bad one, and you cannot start it at minute 84.
What broke on mine
Four failures out of my own commit history, none of which appear in any build video.
Prerendered slugs that 404 at build. generateStaticParams and the single-post fetcher had drifted apart — the page filtered on published and business_id, the params list did not. Next.js dutifully prerendered slugs the page then refused to render. The fix was one shared function, not a patch in each route:
// lib/slugs.ts — used by app/sitemap.ts AND every generateStaticParams
export async function getBlogSlugs() {
const { data } = await supabase
.from("blog_posts")
.select("slug, published_at")
.eq("published", true)
.eq("business_id", SANTOSH_BUSINESS_ID)
.order("published_at", { ascending: false });
return data ?? [];
}
A production build that failed on TypeScript errors the dev server never showed. next dev is forgiving; next build is not. Run the production build before you believe the deploy — on this repo it is the whole test suite.
A canonical hostname pointing at the host that redirects rather than the one Vercel serves. One constant, three months.
Seed SQL that would not run, because a dollar-quoted block collided with a $$ sequence inside a post body. Content pipelines break in ways application code does not, which is one reason my agents write proposals into a review queue rather than holding write access to live tables.
None of these are exotic. All four are in the gap between "it works on my screen" and "someone else is using it."
The honest ratio
There is an old aphorism for this, attributed to Tom Cargill of Bell Labs and published in Jon Bentley's September 1985 Communications of the ACM column, "Bumper-Sticker Computer Science": the first 90% of the code accounts for the first 90% of the time, and the remaining 10% accounts for the other 90%. It is a joke about estimation rather than a measurement, and I am citing it as one.
My own arithmetic, with the assumption stated in the sentence: take the 85 minutes above and add up the tail rows in the table — roughly 3-4 hours of RLS, 20 minutes a screen across ten screens, half a day of domain and email verification, an hour or two of caching. Call it 500 minutes. Then 85 / (85 + 500) is 14.5%. The filmed part is about a seventh of the work, on the smallest thing I have shipped on this stack. On the client platform in four platforms in 25 days, the ratio was nowhere near that.
That is not an argument against the stack. I picked it deliberately and would pick it again — the 20% used to be the entire project. It is an argument against planning around the demo.
Use the two hours to decide, not to ship
The right use of an 85-minute build is not shipping in 85 minutes. It is deciding in 85 minutes.
Build the thing, look at it, and find out whether the idea survives contact with a working version. Most of mine have not. Finding that out in an afternoon instead of a month is the actual win, and it is a bigger one than the deploy time.
Then budget the real timeline for the ones that survive — rows 6 to 9 included, in the estimate you hand the client, before they turn into a surprise.
FAQ
Can you really build a full-stack SaaS in under 2 hours with v0 and Supabase?
You can build a live, authenticated, read-write app in about 85 minutes. That is not the same thing as a SaaS: no row-level security, no billing, no verified sending domain, one UI state per screen. Everything in rows 1 to 5 of the table above genuinely fits in two hours. Nothing in rows 6 to 9 does.
How long does it actually take to build a SaaS with Next.js and Supabase?
For the smallest real thing, budget the 85 minutes plus roughly six more focused hours before you would put someone else's users on it. For anything with multiple user types, payments, or a migration from an existing system, the tail runs into days and weeks — and it is the tail that decides the launch date, not the scaffold.
What takes the longest in a Supabase and Next.js build?
Row Level Security, on every build I have done. It is the first stage where you have to think rather than type, the errors are silent instead of loud, and Postgres gives you no CREATE POLICY IF NOT EXISTS to make the migration re-runnable. Budget an afternoon the first time and an hour after that.
Is Supabase production-ready for a real SaaS?
For everything I have shipped, yes — with the caveat that the defaults are open, not closed. Enabling RLS and revoking the anon grants on every table is a step you perform, not a state you are handed. Read the grants, not just the policies.
Planning around a two-hour build? I can give you the version of the estimate that includes rows 6 to 9, before you quote it to anyone. See my services or get in touch.
Get the AI Automation Playbook
The real architecture behind a 6-agent AI content team — what it saves, what it gets wrong, and the propose-then-approve pattern that makes it safe to trust.
Browse all free guides →Run this on your own numbers
Content ROI & Payback Calculator — free, no signup, runs in your browser.
Want to implement this with guidance?
Santosh helps founders turn insights like this into real systems.
External Resources
Further Reading & Tools
Make.com Blog
No-code automation tutorials, workflow templates, and integration guides
Zapier Learn
Automation best practices, productivity guides, and tool integrations
OpenAI Platform
API documentation for building AI-powered automation workflows
n8n Blog
Open-source workflow automation guides and integration examples