9 September 2025 · 5 min read
How I prototype as a Y-Combinator founder
TL;DR: I use Next.js (hosted in Vercel), Supabase and Lumen. The entire stack is free to use.
All the code is available in Github
A live version can be found at https://lumen-nextjs-starter.vercel.appWhy I chose this stack
NextJS: Easy to use and free to deploy in Vercel. AI knows it very well so it makes it easy to debug and build. I have used it extensively so it is the easiest to use for me. Trying Vite or other alternatives would only slow me down.
Supabase: generous free tier that is good enough for demos. Integrated auth makes it super easy to ship fast.
Lumen: out of the box credit system to limit AI usage per account. If you connect Stripe, you also get payments working without any extra effort.
Supabase configuration
Supabase per-user JSON store with RLS
This is probably the most controversial part. I just make each user have a big json storing all the user data so I can reapidly iterate. Most of the prototypes I have made never got out of the demo stage so I only add proper tables and optimizations after I get the first paying customer.Table & RLS
Create a table public.user_data
with data
as jsonb
and user_id
as uuid
defaulting to auth.uid()
. Enable RLS and add a policy that allows all operations only when user_id = auth.uid()
.
-- Table
create table if not exists public.user_data (
user_id uuid not null default auth.uid(),
data jsonb not null,
primary key (user_id)
);
-- Row Level Security
alter table public.user_data enable row level security;
create policy "Users can manage their own user_data"
on public.user_data
for all
to authenticated
using (user_id = auth.uid())
with check (user_id = auth.uid());
Supabase storage: per-user "files" bucket policy
Create a storage bucket named files
and add a policy that restricts access to objects inside a folder named after theuser_id
. Save files to paths like${auth.uid()}/avatar.png
so only the owner can read and write them.
-- Create private bucket (no-op if it already exists)
insert into storage.buckets (id, name, public)
values ('files', 'files', false)
on conflict (id) do nothing;
-- Storage RLS policy (applied to storage.objects)
create policy "Users can manage files in their own folder"
on storage.objects
for all
to authenticated
using (((bucket_id = 'files'::text)
and ((select (auth.uid())::text as uid) = (storage.foldername(name))[1])))
with check (((bucket_id = 'files'::text)
and ((select (auth.uid())::text as uid) = (storage.foldername(name))[1])));
Lumen configuration
Since many of my demos are AI based, I always want to put some limits per user to prevent abuse and track usage. I make a free account in Lumen and skip adding Stripe API keys for now (or you can add them if you want to validate that users want to pay already). Then I create a free plan and I add the credits I want to allow per month.
Wrapping up
This is the easiest way I found to build MVPs fast. Althought we built Lumen to process payments, it also helps build credit systems for AI even before implementing payments (I wish I had Lumen when I built the demo that got us into Y-Combinator).Please let me know what you think, I reply to all the people who reach out! Email me at ramon@getlumen.dev or message me on LinkedIn or Twitter

Ramon Garate
Founder of Lumen