Href Creative Docs
HREF Team Docs

Local development

Everything runs on your Mac for v1. No VPS, no Vercel deploy, no DNS.

Prerequisites

ToolVersionNotes
Node22.x (LTS)Managed by nvm is fine
npm10+Comes with Node
Docker DesktoprecentHosts the Supabase OSS stack
Self-hosted SupabaserunningAt /Users/angelom/apps/supabase-local/, see Phase 1 of the self-hosted Supabase plan
Supabase CLIlatestbrew install supabase/tap/supabase (only needed for db diff, optional for v1)

Confirm Supabase is running:

docker ps --filter "name=supabase" --format "{{.Names}}"

You should see supabase-kong, supabase-db, supabase-auth, supabase-realtime, and friends.

First-time setup

git clone git@github.com:amarasa/href-crm.git
cd href-crm
npm install
cp .env.example .env.local   # then fill in keys (see below)

.env.local needs:

VariableSource
NEXT_PUBLIC_SUPABASE_URLhttp://localhost:8000 for the local Mac stack
NEXT_PUBLIC_SUPABASE_ANON_KEYFrom /Users/angelom/apps/supabase-local/.secrets-backup.txt (ANON_KEY)
SUPABASE_SERVICE_ROLE_KEYSame file (SERVICE_ROLE_KEY)
RESEND_API_KEYThe hrefcreative.com Resend account's key
RESEND_FROM_EMAILcrm@hrefcreative.com
RESEND_REPLY_TOsupport@hrefcreative.com
NEXT_PUBLIC_APP_DOMAINlocaltest.me:3001 for local
NEXT_PUBLIC_APP_PROTOCOLhttp for local

Apply the schema

The Supabase CLI's db push is designed for the cloud workflow. For our self-hosted stack, apply migrations directly via psql inside the Postgres container:

docker exec -i supabase-db psql -U postgres -d postgres < supabase/migrations/0001_initial_schema.sql

Re-running an already-applied migration will throw "already exists" errors; that's fine, the existing tables stay put. To start fresh, drop the schema first:

docker exec -i supabase-db psql -U postgres -d postgres -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"

(Be careful: that wipes everything in public.)

Provision a test tenant

npm run provision-tenant -- acme "Acme Family Law" you@example.com

The output prints the tenant ID, the one-time API key, the splash URL, the admin invite URL, and a curl example for testing the intake API. Save the API key somewhere; it cannot be retrieved later.

Run the dev server

npm run dev

Pinned to port 3001 (HQ owns 3000). On first run, Next downloads compilers, installs Turbopack, etc. Subsequent starts are sub-second.

Visit:

  • http://acme.localtest.me:3001/ — Acme's branded splash
  • http://acme.localtest.me:3001/invite/<token> — the invite acceptance page (URL printed by provision-tenant)
  • http://localtest.me:3001/docs — the docs site (this page lives there)

Common commands

npm run dev                                                # dev server (port 3001, Turbopack)
npm run typecheck                                          # tsc --noEmit
npm run test                                               # Vitest (unit)
npm run test:e2e                                           # Playwright (e2e)
npm run provision-tenant -- <slug> "<name>" <admin-email>  # create a tenant

Reconfiguring Supabase Auth SMTP

The CRM relies on the local Supabase Auth sending magic-link emails through Resend. The relevant settings live in /Users/angelom/apps/supabase-local/docker/.env:

SITE_URL=http://localtest.me:3001
ADDITIONAL_REDIRECT_URLS=http://*.localtest.me:3000,http://*.localtest.me:3001,...
SMTP_ADMIN_EMAIL=crm@hrefcreative.com
SMTP_HOST=smtp.resend.com
SMTP_PORT=465
SMTP_USER=resend
SMTP_PASS=<resend-api-key>
SMTP_SENDER_NAME=HREF CRM

After editing, restart the auth container:

cd /Users/angelom/apps/supabase-local/docker
docker compose up -d auth

(up -d auth recreates the auth container with the new env vars; restart auth would not pick them up.)

Troubleshooting

SymptomLikely causeFix
Splash 404s with no bannerSubdomain does not resolve to a tenantprovision-tenant first, or check the slug spelling
"This account is not a member of this workspace"User signed up but no tenant_members rowUse the invite link, do not just sign up
Magic link email never arrivesSupabase Auth SMTP misconfiguredCheck docker logs supabase-auth for SMTP errors
Dev server picks port 3002Port 3001 already in usepkill -f "next dev" and start fresh
localtest.me does not resolveSome VPNs/DNS hijack *.meAdd 127.0.0.1 localtest.me acme.localtest.me to /etc/hosts as a fallback
unstable_cache returns stale tenantCached value from lookupTenant is held for 60 secondsWait, or call revalidateTag("tenants") after mutations

Resetting state

To wipe the CRM data without nuking the whole Supabase OSS stack:

TRUNCATE tenants RESTART IDENTITY CASCADE;

Run that via Supabase Studio (http://localhost:8000) or docker exec -i supabase-db psql -U postgres -d postgres. Cascades through every related table.

On this page