Hosted (Vercel + Supabase)
The tried-and-true managed path for GiftWrapt is Vercel for the app, Supabase for Postgres, and Supabase Storage for image uploads. Three services, one repo, no infrastructure to babysit.
This is the configuration I run in production. It’s the recommended choice if you don’t want to manage a host.
What You’ll Need
Section titled “What You’ll Need”- A Vercel account (free Hobby tier works for a single household).
- A Supabase account (free tier covers a small deployment).
- A fork or clone of shawnphoffman/giftwrapt.
Deploy
Section titled “Deploy”-
Create a Supabase project.
From the Supabase dashboard, New project. Pick a region close to your users and a strong database password (you won’t need to copy it - Supabase wires the connection string for you).
Wait for the project to provision (60-90 seconds).
-
Enable Supabase Storage S3 access.
In your project’s dashboard:
- Project Settings > Storage > S3 Connection -> click Enable.
- Note the Region value Supabase shows.
- Generate an Access Key ID and Secret Access Key. Save both somewhere safe.
-
Create a storage bucket.
- Storage > New bucket -> name it
giftwrapt(or anything you like; you’ll plug the name into env later). - Public vs. private doesn’t matter for GiftWrapt - the app proxies images through
/api/files/*by default.
- Storage > New bucket -> name it
-
Get the Postgres connection string.
- Project Settings > Database > Connection String > URI.
- Use the Connection pooling variant (the “session” or “transaction” mode pooler) for a production app on Vercel. Copy it.
-
Import the repo into Vercel.
From vercel.com/new:
- Import Git Repository -> point at your fork of
shawnphoffman/giftwrapt. - Framework Preset: Vercel should detect TanStack Start automatically. If it doesn’t, set the build command to
pnpm vercel-build(runs migrations + Vite build) and the output directory to.output/public. - Don’t click Deploy yet - set env vars first.
- Import Git Repository -> point at your fork of
-
Set the environment variables.
In the Vercel project’s Environment Variables section, add:
Var Value DATABASE_URLThe pooled Postgres URI from step 4 BETTER_AUTH_SECRETopenssl rand -base64 32(paste the output)STORAGE_ENDPOINThttps://<project-ref>.supabase.co/storage/v1/s3STORAGE_REGIONThe region from step 2 (e.g. us-east-1)STORAGE_BUCKETgiftwrapt(or whatever you named it in step 3)STORAGE_ACCESS_KEY_IDThe S3 access key from step 2 STORAGE_SECRET_ACCESS_KEYThe S3 secret from step 2 STORAGE_FORCE_PATH_STYLEtrueBETTER_AUTH_URLdoesn’t need to be set on production deploys - it’s auto-derived fromVERCEL_PROJECT_PRODUCTION_URL. Set it explicitly only if you’re binding to a custom domain. -
Deploy.
Click Deploy in Vercel. The build runs
pnpm vercel-build, which applies migrations against your Supabase Postgres before bundling the app. -
Sign up.
Open the assigned
*.vercel.appURL. The first user to register is auto-promoted to admin.
That’s it. App is up, image uploads work, cron runs daily.
What’s Running
Section titled “What’s Running”| Service | Where | Notes |
|---|---|---|
| App | Vercel | TanStack Start running on Vercel Functions. |
| Database | Supabase Postgres | Migrations run on every build via pnpm vercel-build. |
| Storage | Supabase Storage (S3 API) | Bucket created in step 3. The app proxies images via /api/files/* unless you set STORAGE_PUBLIC_URL. |
| Cron | Vercel Cron | Daily schedules from vercel.json. See Cron and scheduling. |
| Optional | See Emails. Add RESEND_API_KEY to your Vercel env vars to enable. |
Custom Domain
Section titled “Custom Domain”In Vercel Settings > Domains, add your domain and point it per Vercel’s instructions. Once the domain is live:
- Set
BETTER_AUTH_URLto the canonical HTTPS URL (e.g.https://giftwrapt.example.com) in your Vercel env vars. - Redeploy.
If you serve from multiple hostnames (custom domain + the Vercel-assigned URL), add the others to TRUSTED_ORIGINS instead of switching BETTER_AUTH_URL.
Direct Image URLs (Faster, Optional)
Section titled “Direct Image URLs (Faster, Optional)”By default the app proxies image fetches through /api/files/<key>. That works fine but spends a Vercel function invocation per image. To hand clients direct Supabase URLs and skip the proxy:
- In Supabase Storage, mark the bucket as public (or set up a custom RLS policy that allows public read).
- Set
STORAGE_PUBLIC_URLin Vercel to your Supabase Storage public base URL (something likehttps://<project-ref>.supabase.co/storage/v1/object/public/giftwrapt). - Redeploy.
Existing uploads pick up the new URL on next render.
The vercel.json That Ships With the Repo
Section titled “The vercel.json That Ships With the Repo”For reference, the vercel.json in the repo declares:
- The build command (
pnpm vercel-build). - Cron schedules for all five
/api/cron/*endpoints at daily UTC cadences (Hobby-tier compatible). - An auto-generated
CRON_SECRET.
Don’t edit it unless you have a reason to. On Vercel Pro you can crank specific crons to sub-daily; on Hobby you’re capped at daily.
Updating
Section titled “Updating”Push to main (or merge a release PR) - Vercel auto-deploys. Migrations run as part of the build, so schema changes ship with the app.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause |
|---|---|
| ”Invalid origin” on sign-up | Custom domain set but BETTER_AUTH_URL not updated, or you’re hitting a preview deploy URL. Set BETTER_AUTH_URL to the production URL. |
| Image uploads return 503 | One of the STORAGE_* vars is wrong. Check Project Settings > Storage > S3 Connection in Supabase to confirm the endpoint and region match what you pasted into Vercel. |
| Migrations fail on build | DATABASE_URL is the un-pooled “direct” URL instead of the pooled one. Switch to the Connection pooling URI from Supabase. |
| Cron not firing | Vercel’s free tier supports daily-only schedules - that’s the default. If you’ve edited vercel.json to use a sub-daily cron, you need Vercel Pro. |
Other Hosted Platforms
Section titled “Other Hosted Platforms”- Hosted (Railway) - one-click multi-service template with internal cron services
- Hosted (Render) - one-click
render.yamlblueprint with managed Postgres - Self-hosting with Docker - full control, runs anywhere
I run Vercel + Supabase in production, but Railway and Render are both documented and supported one-click paths.