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.app

Why 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().

sql
-- 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.

sql
-- 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.
Create free plan with Lumen

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

Ramon Garate

Founder of Lumen