Infrastructure

How this site is built — and how you’d build it.

No platform team, no microservice sprawl. A small, boring, durable stack one person can hold in their head — plus the real commands, packages, and Claude Code prompts to stand one up yourself in an afternoon.

The stack

What’s actually running.

Six choices, each picked for what it does best — and each a click from its docs. The whole thing fits in one repo on one database.

Frontend

Vite · React 19 · TypeScript · Tailwind v4. Static pages and hydrated apps from one build.

Hosting

Vercel. Static output served off the filesystem; serverless functions only where they earn it.

Data

Supabase — Postgres, Auth, Storage, Realtime. One database for the whole studio.

Security

Row-Level Security. Every row is pinned to its owner in the database, not the app layer.

AI

The Anthropic API for product features — and Claude Code as the dev environment that built this.

Commerce

Stripe for payments and subscriptions; web-push for notifications. Money never touches our servers.

Architecture

One repo, a handful of vendors.

The browser talks to the Vercel edge; the edge talks to Postgres (guarded by row-level security), Stripe, and Claude. No message bus, no service mesh, nothing to page anyone at 3am.

BrowserReact · Vite · TSstatic + hydratedVercel EdgeCDN · functionsstatic firstSupabasePostgres · RLSone source of truthStripeCheckout · webhookspaymentsAnthropicClaude APIproduct AIHTTPSRLS · JWTwebhookstream

Data

One Postgres, held by the database itself.

Isolation isn’t enforced by careful application code — it’s enforced by Postgres. Row-Level Security pins every row to its owner, so a forgotten where clause can’t leak data. The calm version of multi-tenancy.

Without RLS

every endpoint
// one missed check anywhere and the castle fallsif (user.id !== note.owner) {  throw new ForbiddenError();}

Authorization lives in app code. Every endpoint has to remember.

With RLS

schema.sql
-- the database simply won't return the rowcreate policy "owners read their own notes"  on notes for select  using (owner = auth.uid());

Authorization lives in Postgres. The app can’t leak what the DB won’t hand over.

Build it yourself

A site like this, from scratch.

The actual recipe — the same pattern behind every product here. Copy the snippets, follow the links. About fifteen minutes to a multi-page site on a real domain, backed by a real database that keeps each user’s data to themselves.

01

Scaffold a Vite app

One command gives you React + TypeScript with hot reload — your whole frontend toolchain.

zsh
npm create vite@latest my-site -- --template react-tscd my-sitenpm installnpm run dev
Vite — Getting Started
02

Add Tailwind v4

Tailwind v4 is one Vite plugin now — no config file, no PostCSS. Define your theme in CSS with @theme.

zsh
npm install tailwindcss @tailwindcss/vite
vite.config.ts
import { defineConfig } from 'vite';import react from '@vitejs/plugin-react';import tailwindcss from '@tailwindcss/vite'; export default defineConfig({  plugins: [react(), tailwindcss()]});
src/index.css
@import "tailwindcss"; @theme {  --color-ink-950: #09090b;  --color-violet-600: #7c3aed;  --font-display: "Plus Jakarta Sans", sans-serif;}
Tailwind — Install with Vite
03

Add data with Supabase + RLS

Install the client and wire it with env vars. The anon key is safe to ship to the browser — because RLS makes Postgres refuse any row that isn’t yours.

zsh
npm install @supabase/supabase-js
src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'; export const supabase = createClient(  import.meta.env.VITE_SUPABASE_URL,  import.meta.env.VITE_SUPABASE_ANON_KEY);
postgres · schema.sql
create table notes (  id         uuid primary key default gen_random_uuid(),  owner      uuid references auth.users default auth.uid(),  body       text,  created_at timestamptz default now()); alter table notes enable row level security; create policy "owners manage their own notes"  on notes for all  using (owner = auth.uid())  with check (owner = auth.uid());
04

Add an API route

Drop a function in /api. Validate input with zod so bad requests never reach your logic. Vercel turns the file into a serverless endpoint automatically.

api/contact.ts
import { z } from 'zod'; const Body = z.object({ email: z.string().email(), message: z.string().min(1) }); export async function POST(req: Request) {  const parsed = Body.safeParse(await req.json());  if (!parsed.success) return Response.json({ error: 'bad input' }, { status: 400 });  // ...do the work  return Response.json({ ok: true });}
Vercel — Functions
05

Deploy to Vercel

One CLI, one command for a preview URL, one flag to ship. Lock your security headers once, for every path, and never think about them again.

zsh
npm i -g vercelvercel          # build + preview URLvercel --prod   # ship it
vercel.json
{  "buildCommand": "npm run build",  "headers": [    {      "source": "/(.*)",      "headers": [        { "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },        { "key": "X-Frame-Options", "value": "DENY" },        { "key": "X-Content-Type-Options", "value": "nosniff" }      ]    }  ]}

Claude Code prompts

Or just describe it.

This whole site was built with Claude Code in the terminal. Here are real, copy-paste prompts that map to each step above — point Claude Code at an empty folder and let it scaffold, wire, and ship.

Claude Code

Scaffold a Vite + React + TypeScript app. Configure Tailwind v4 through the @tailwindcss/vite plugin and a dark theme using CSS @theme tokens for colors and fonts.

Claude Code

Add Supabase Google sign-in: a useAuth() hook with getSession + onAuthStateChange, a sign-in button, and logic that strips ?code= from the URL after the OAuth redirect.

Claude Code

Write a Postgres migration for a notes table (id, owner, body, created_at), enable row-level security, and add policies so a user can only read and write their own rows, keyed on auth.uid().

Claude Code

Create a Vercel serverless function at /api/contact that validates the JSON body with zod, returns 400 on bad input, and add the route plus strict security headers (CSP, HSTS, X-Frame-Options) to vercel.json.

Claude Code

Make this multi-page site crawlable: add a build step that renders each page to static HTML with renderToString and injects it into the built index.html, then hydrate on the client.

Claude Code

Set up a daily Vercel Cron that calls /api/digest at 8am, and give me a git-free deploy I can run by hand: build locally, then ship with `vercel --prod`.

Tip: keep a CLAUDE.md at the repo root describing your stack and conventions — Claude Code reads it on every run, so the output matches your house style.

Packages

What you’ll actually install.

The short list that does the heavy lifting — nothing exotic, all maintained, all in production here.

the runtime essentials
npm install @supabase/supabase-js @anthropic-ai/sdk zod motionnpm install -D @tailwindcss/vite tailwindcss vite
@supabase/supabase-jsTyped Postgres / Auth / Storage client. The browser-safe anon key + RLS does the rest.
@anthropic-ai/sdkOfficial Claude API SDK — messages, streaming, and tool use.
@tailwindcss/viteTailwind v4 as a single Vite plugin. No config file, no PostCSS.
motionFramer Motion — declarative animations, gestures, scroll. (The home hero runs on it.)
zodSchema validation for API bodies and env vars. Parse, don’t trust.
stripePayments, subscriptions, and webhooks, server-side.
web-pushVAPID web-push notifications with no third-party service.
vercelCLI for preview + production deploys, env vars, domains, and cron.

That’s the whole machine. Want to see where this pattern ends up, or build it with us? See what we do or join the studio.