Database Setup
diy-analytics stores everything in PostgreSQL via Drizzle ORM, accessed with the postgres-js driver (lib/db.ts). Any Postgres 14+ works — there's no provider-specific SDK, just a DATABASE_URL connection string.
Read this before you pick a host. If you deploy diy-analytics anywhere other than Vercel, visitor tracking will silently fail — the dashboard will load fine, but no data will ever arrive. See Ingestion requires Vercel below before you commit to a hosting platform.
Connecting a provider
Set DATABASE_URL in .env.local (or your hosting platform's environment variables), then apply the schema:
A quick note on connection pooling, since Neon and Supabase both ask you to choose between two connection strings: your app can only hold so many direct connections to the database open at once, and a serverless host like Vercel can spin up many short-lived instances of your app at the same time — each wanting its own connection. A "pooler" sits in front of the database and shares a smaller set of real connections across all of them, so you don't run out. That's why, below, you're told to copy the pooled string rather than the direct one — the direct one will work at low traffic and then start failing with connection errors as traffic grows.
Neon
Use the pooled connection string — the one with -pooler in the hostname, not the direct one:
You don't need to configure anything else for this to work — lib/db.ts already talks to pooled connections correctly out of the box.
Neon pauses its database automatically after a period of no traffic to save resources. The next query after a pause takes a little longer while it wakes back up (a "cold start"). If that occasional delay matters for your traffic, you can extend or disable this auto-pause in the Neon console — it's a setting on your Neon project, not something you change in diy-analytics.
Supabase
Grab the connection string from Settings → Database → Connection string, and use Transaction pooler mode (port 6543), not Session mode (port 5432) — same pooling reasoning as Neon above:
That's the only setting you need. No other Supabase env vars (SUPABASE_URL, SUPABASE_ANON_KEY, etc.) are required — diy-analytics only speaks plain Postgres, it never touches Supabase's client library or its Auth/API layer.
Vercel Postgres
Use the connection string Vercel provisions for you (Project → Storage tab) as-is:
Railway, self-hosted, or other Postgres
Any standard connection string works:
If you're running your own Postgres behind a connection pooler in transaction mode, the same handling described above applies automatically — no code changes needed on your end.
Ingestion requires Vercel
Your database can be any provider listed above. But diy-analytics — the app itself — needs to be deployed on Vercel for tracking to actually work. These are two separate things, and mixing them up is the most common way a new deployment ends up with an empty dashboard.
Here's why: when a visitor loads your website, the tracking script sends its data to /api/track. On Vercel, that request gets handed off to a background job (built on Vercel Queues) that safely writes it into your database, even under heavy traffic. That background job is a piece of Vercel's own infrastructure — it doesn't exist, and can't be recreated, on Docker, Railway, or a plain Node server.
In plain terms: if you deploy diy-analytics anywhere except Vercel, the dashboard will look fully working, but /api/track will fail to record events — even if your database is one of the fully-supported providers above. There's no error shown to your site's visitors; you'd just see zero data in your dashboard. If you're set on self-hosting the app itself rather than using Vercel, confirm you're comfortable with this limitation before investing time in a deployment.
Everything else — the dashboard itself, every other API route, and the daily rollup cron — has no Vercel-specific dependency and runs fine on any Node host.