Why JED is one fresh repository per client
I build a lot of client sites on the same base. I stopped trying to run them from one shared codebase and made JED a template you copy for each client instead.

Most of my client work starts the same way. Someone needs a site with a few pages, a blog, a contact form, and a way for their team to edit content without calling me. The stack is almost always TanStack Start on the front, Supabase for the database and sign-in, and Vercel for hosting.
For a while I treated that shared base as something to centralize. One codebase, many clients, with settings to tell them apart. It didn't last. JED, my starter, now works the other way: every client gets their own repository created from the template, their own Supabase project, and their own Vercel project.
What a fresh copy looks like
Starting a new client site is a short list:
git clone https://github.com/<you>/<client-site>.git
cd <client-site>
npm ci
cp .env.example .env.local
npx supabase link --project-ref <client-project-ref>
npx supabase db pushThe repository was created from the JED template on GitHub, so it starts with the full codebase and none of the starter's commit history. db push applies the migrations in supabase/migrations/ to the client's own database. After that, I sign up, grant my account the admin role with one SQL insert, and set the site name, accent colour, and logo in the admin settings.
Clients drift, and that's fine
The main reason for separate copies is that client sites don't stay the same. One client wants a custom booking page. Another needs a content type nobody else has. A third never changes anything after launch.
In a shared codebase, every one of those becomes a flag or a branch in someone else's code path. In separate repositories, the booking page is just a route in that client's repo. I can change the schema for one client with a migration that only runs on their database. Nobody else is affected, and nobody else's tests break.
Data stays apart by default
Each client also has their own Supabase project. Their users, content, uploaded media, and contact messages live in a database that has nothing to do with anyone else's. If a client asks where their data is, the answer is one project they can be given access to.
It also limits the damage of mistakes. A bad migration or a leaked key affects one client, not all of them. The keys are per project too, set as environment variables in that client's Vercel project and never shared.
Pulling improvements back in
The obvious cost is that fixes don't spread on their own. When I improve the starter, existing client sites don't get it automatically. I handle that with a plain git remote:
git remote add starter <url-of-the-jed-repo>
git fetch starter
git log HEAD..starter/main --onelineFrom there I merge or cherry-pick what makes sense for that client, apply any new migrations with npx supabase db push, and run the checks before deploying. It's manual on purpose. Not every improvement belongs in every client site, and I'd rather decide than have it decided for me.
Keeping the starter honest
Because every client starts from the same template, the template has to be in good shape. JED runs CI on every pull request: Biome, a TypeScript check, the test suite, a production build, and an npm audit. Each migration has a plain-English entry that shows up in the admin's SQL templates screen, and a test fails if one is missing. Those checks are what make "copy it again for the next client" safe.
When I'd choose differently
If I were building one product with many customers, I'd use a single multi-tenant codebase. That's a different job. Client sites are many small products with one customer each, and for that, a fresh copy per client keeps every project simpler to reason about, change, and hand over.
- JED
- Architecture
- Supabase
- Vercel