Self-hosting
Spotcraft needs two things: a Supabase project (Postgres + Auth) and somewhere to host the dashboard (a Node server, built with Nitro).
1. Create a Supabase project
Section titled “1. Create a Supabase project”Create a project at supabase.com, in a region close to where you’ll host the dashboard (every page makes several database round trips).
Then link it and apply the migrations:
pnpm exec supabase link --project-ref <your-project-ref>pnpm exec supabase db push --dry-run # review what will be appliedpnpm exec supabase db push2. Configure Auth
Section titled “2. Configure Auth”Spotcraft accounts are invite-only. Configure either in the Supabase dashboard or as code.
Dashboard (Authentication):
- Sign In / Providers: turn off Allow new users to sign up.
- URL Configuration: set Site URL to your dashboard’s URL, and add
https://<your-domain>/**to Redirect URLs (reset and invite links return to/auth/callbackand/accept-invite).
As code: supabase/config.toml already disables sign-ups. Uncomment the [remotes.production]
template at the end of the file, fill in your project ref and URLs, and apply it — the CLI shows a diff
and asks before changing anything:
pnpm exec supabase config push3. Set up email (SMTP)
Section titled “3. Set up email (SMTP)”Supabase’s built-in email service only delivers to members of your Supabase team and sends a few emails per hour, so invites to anyone else fail. Configure a provider (Resend, Postmark, SendGrid, Amazon SES, …) under Authentication → Emails → SMTP Settings.
With custom SMTP you can also customize the email templates. Using token_hash links lets reset and
invite links work on any device; /auth/callback already accepts them.
4. Create the first admin
Section titled “4. Create the first admin”Users aren’t configuration. Create yourself in the Supabase dashboard (Authentication → Users → Add user, with Auto Confirm User), then give that account the admin role in the SQL editor:
update auth.usersset raw_app_meta_data = coalesce(raw_app_meta_data, '{}'::jsonb) || '{"role": "admin"}'::jsonbwhere email = 'you@example.com';Sign out and back in so your session picks up the role. From then on, invite everyone else from the Users page.
5. Host the dashboard
Section titled “5. Host the dashboard”Set these variables on your host — details in Environment:
| Name | Value |
|---|---|
VITE_SUPABASE_URL |
https://<ref>.supabase.co (inlined at build time — rebuild after changing) |
VITE_SUPABASE_PUBLISHABLE_KEY |
your project’s publishable key |
SUPABASE_SECRET_KEY |
your project’s secret key — keep it secret |
DATABASE_URL |
the shared pooler connection string — see below |
DATABASE_URL should be Supabase’s shared pooler (Supavisor) in transaction mode, copied from the
project’s Connect panel:
postgresql://postgres.<ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgresdb.<ref>.supabase.co — the direct connection and the dedicated pooler — is IPv6-only unless you
enable Supabase’s paid IPv4 add-on, so it fails from IPv4-only hosts (including Vercel) with
getaddrinfo ENOTFOUND. Percent-encode special characters in the password.
Vercel
Section titled “Vercel”Import the repository; the framework is detected as TanStack Start, and Nitro emits Vercel’s build
output automatically. Add the variables above for Production (and Preview if you use it), and set the
Function Region close to your Supabase region. The function runtime is pinned to Node 26 in
vite.config.ts.
Any Node host
Section titled “Any Node host”pnpm install --frozen-lockfilepnpm build # with the VITE_* variables setnode .output/server/index.mjs # reads PORT and the server variables from the environmentUpdating
Section titled “Updating”Pull the latest version, then apply new migrations before deploying code that depends on them:
pnpm exec supabase db push --dry-runpnpm exec supabase db pushTroubleshooting
Section titled “Troubleshooting”- Server errors are logged with their full cause chain (lines starting with
[server-fn] failed); the browser only sees a generic message. On Vercel:pnpm exec vercel logs --project <name> --since 1h --query 'level:error'. getaddrinfo ENOTFOUND db.<ref>.supabase.co— use the shared pooler URL (see above).- Invite or reset emails never arrive — set up SMTP (step 3), and check that the redirect URLs include your domain.
- Signed in but only seeing Account — your user has no
admin/editorrole yet (step 4).