To have a site of your own — your pharmacy's, an internal tool, a dashboard with your own sales — you need four pieces, and only one of them is AI. It is worth understanding what each one does before touching any of them, because most of the mess comes from asking one to do another one's job.
| Piece | What it does | Without it… |
|---|---|---|
| GitHub | Holds the code and all of its history. Every change is dated and can be undone. | A change that breaks something cannot be rolled back. It is the piece people skip most and the one that costs most. |
| Vercel | Takes what is in GitHub and serves it on the internet, with its domain and its certificate. | You have the code and nobody sees it. |
| Supabase | The database and the user accounts. What has to be kept and who is allowed to read it. | The site works but remembers nothing: not an order, not a customer, not an account. |
| Claude Code or Codex |
Writes the code, inside your own project. | You write it, or you commission it. |
Why this order
GitHub first, always, even if the project is two files. With an agent writing, the question is not whether it will break something: it is whether, on the day it does, you can go back. Without history the answer is no — and there is no warning, because an overwritten file looks exactly like one that never existed.
Then Vercel, connected to GitHub. From that point on every change you push publishes itself, within a minute. That is exactly the good part and exactly the trap: there is no "right, now publish" button. What you pushed is live.
Supabase comes in when there is something to remember. And Claude Code or Codex as soon as the repository exists — not before, because its context is the project.
What it costs
All four have a free tier, and for a pharmacy site with normal traffic the free tier is enough. I am deliberately not putting the actual figures here: they change every few months and a lesson with stale numbers is worse than one with none. What does not change is the shape:
- GitHub is free for what you are going to do, private repository included.
- Vercel charges for traffic and for server functions. A site that serves pages does not go near the limit; one that calls an AI on every visit does.
- Supabase charges for storage and for an active database. Watch out for one thing that is not a price: it pauses idle free projects.
- Claude Code / Codex is the only one with a certain cost, and it is per use.
The setup, step by step
-
Create the repository on GitHub
Private. And with this inside from the very first commit: a
READMEsaying what this is — the agent will read it — and a.gitignore.What NEVER goes into the repository: keys, tokens, passwords,
.envfiles. A key pushed to GitHub stays in the history even if you delete it in the next commit: it has to be rotated, not deleted. -
Connect Vercel to the repository
You import the repository and that is it: every push to the main branch publishes. What really matters to learn here is two things.
Work in branches. One branch per change, and Vercel gives you a preview URL for THAT branch. You can watch it work without touching what is live.
And environment variables live in Vercel, not in the code. That is where the keys belong. Your server reads them there and the browser never sees them.
-
Supabase: the database and the accounts
Two things that are widely misunderstood and have to be understood properly, because whether your pharmacy's data is closed or open depends on them.
The "anon" key is public by design. It goes in the browser, it is visible in the page source, and that is fine — what protects each row is not the key, it is RLS. Anyone telling you to hide the anon key is solving the wrong problem.
And the "service role" key bypasses RLS entirely. That one never goes to the browser: it lives in Vercel's environment variables and only your server uses it. It is the one mistake in this lesson that cannot be undone — whoever holds it holds everything — so if you ever suspect it has leaked, rotate it and move on.RLS on from minute one, table by table. A table with RLS and no policies lets nobody in, which is a visible problem and a one-minute fix. A table without RLS can be read by anyone holding the anon key, which is an invisible one.
And one specific trap: if you turn RLS on and only write a
SELECTpolicy, deletes fail silently — the request comes back with "no error" and deletes nothing. You need one policy per verb. -
Claude Code or Codex, inside the repository
It is not a chat you paste code into: it opens in your project and reads the files. That is why the order matters — with the repository already set up, the agent sees how the existing code is written and writes in that style.
How you steer it, what you ask it for and how you check what comes back is the next lesson.
The four mistakes that cost most
| Mistake | What happens | How to avoid it |
|---|---|---|
The service_role key in the browser |
Anyone can read and write your whole database | Environment variables only. If in doubt, rotate it |
| A table without RLS | It can be read in full with the public key | RLS when you create the table, not later |
| Working on the main branch | Every half-finished attempt goes live | One branch per change; Vercel gives you its preview URL |
A committed .env |
The key is in the history for good | .gitignore from the first commit |
service, secret and key. If anything comes up
that is not Supabase's anon key, you have a problem this afternoon.
The whole map: what each piece does
Before touching anything it helps to have the whole picture. There are four pieces and each one does exactly one thing — and that separation is why the whole setup can be maintained without a team.
| Piece | The one question it answers | What it is not |
|---|---|---|
| GitHub | "What did this file say three weeks ago?" | It does not serve the site. A repository is not a server |
| Vercel | "How does this reach whoever visits?" | It stores nothing. Every visit starts from zero |
| Supabase | "Where does the stuff worth remembering live?" | It is not a whole backend. It is a database plus accounts |
| The AI agent | "Who writes the code?" | It does not decide what to build. That is yours |
Git in ten minutes: the six commands you actually use
Git is frightening because it has two hundred commands. In practice you use six, and those six carry you for years. The rest is for when something goes wrong, and that is what the person who knows — or the AI — is for.
$ git status
# What have I touched. Type this BEFORE anything else.
$ git checkout -b fix-the-contact-form
# Opens a branch. Nothing you do here touches what is published.
$ git add -A
$ git commit -m "Fix the contact form submission"
# Saves a point you can come back to. With a message that means something.
$ git push -u origin fix-the-contact-form
# Pushes the branch. Vercel gives you a preview URL for THAT branch only.
$ git checkout main && git pull
# Back to the good one, and pull the latest. Before starting anything else.And the two situations that actually scare people
| "I have broken something" | What to do |
|---|---|
| I edited a file and want it back as it was | git checkout HEAD -- path/to/fileNever over the whole tree: that wipes everything you have not committed |
| I have published something broken | Vercel keeps every deployment. Open the previous one and press "promote to production". Fifteen seconds, and no Git needed |
RLS, explained properly for once
It is the Supabase concept people most misunderstand, and the only one where getting it wrong has consequences. It is worth going slowly.
Supabase exposes your database directly to the internet. That sounds insane until you understand the piece that makes it safe: every table carries rules saying, row by row, who may see it and who may touch it. That is RLS (row-level security). It is not an add-on: it is the mechanism.
create table reminders (
id uuid primary key default gen_random_uuid(),
user_id uuid not null default auth.uid(), -- ⚠️ the server sets this
text text not null,
created_at timestamptz not null default now()
);
alter table reminders enable row level security;
-- One policy PER VERB. All four, or one is missing.
create policy "see mine" on reminders for select
using (auth.uid() = user_id);
create policy "create mine" on reminders for insert
with check (auth.uid() = user_id);
create policy "edit mine" on reminders for update
using (auth.uid() = user_id);
create policy "delete mine" on reminders for delete
using (auth.uid() = user_id);for delete policy, Supabase does not error: it reports success and deletes
nothing. The row vanishes from the screen because the browser believes it, and
comes back on reload. It is the perfect failure: no error, no trace, and the
first person to notice is a user.
.select('id')) and do not touch the screen if it comes back empty. With
that, a permissions failure becomes a real error message instead of a silent lie. The same
rule holds everywhere: if the server does not confirm it did something, your
interface cannot say it was done.
The two keys, and why one of them can be published
anon | service_role | |
|---|---|---|
| Where does it go? | In the browser. It is public by design | Only on the server, in environment variables |
| What can it do? | Only what RLS allows | Everything. It bypasses RLS entirely |
| If it leaks | Nothing happens | They have your whole database |
Environment variables, demystified
An environment variable is a value that is not in the code and that the program reads at startup. It is where keys live, and there are three different places to put them depending on what for:
-
On your machine: the
.env.localfileFor working locally. It goes in
.gitignorefrom minute one, before you even write anything inside it — so the window where it gets pushed by accident never exists. -
On Vercel: the environment variables panel
For production. You paste them there and never see them again. Watch the prefix: on Vercel, a variable starting with
NEXT_PUBLIC_orVITE_is sent to the browser. Never put anything there that must stay on the server: the name says so, but it reads fast and lands late. -
On GitHub: repository secrets
For whatever runs on its own (scheduled jobs). It is a different place, with a different list, and you have to remember it exists: a key rotated in Vercel and not in GitHub leaves the nightly job failing with nobody watching.
The first week, day by day
With all of the above in your head, this is what actually works — and in this order, which matters more than it looks:
| Day | What you do | What you have gained |
|---|---|---|
| 1 | Repository, Vercel connected, a page with your name on it published | You have a working URL. That is more than it sounds: everything else is edits |
| 2 | A branch, a change, a preview URL, and you merge it | You now know how to work without fear |
| 3 | Supabase: one table with RLS and its four policies | You can store data and control who sees it |
| 4 | Sign-up and login. Do not write it: Supabase brings it | You have real users |
| 5 | A function with a key in an environment variable | You can now talk to any service without exposing anything |
Before you call this learned
- I know what each of the four pieces does and what happens without it.
- I can tell the anon key (public by design) from the service role key (never leaves the server).
- I turn RLS on when I create the table, with one policy per verb.
- I work in branches and check the preview URL before publishing.
- I have decided — before setting anything up — what data is NOT going to live in there.
← Back to the school Next: coding with Claude Code or Codex →