Running your own
Supabase, sign-in providers and environment variables, end to end.
What you need
- Node 20 or newer, and npm.
- A Supabase project. The free tier is enough.
- A Google Cloud project with an OAuth 2.0 client.
There is no other service to sign up for. Postgres and the realtime relay both come from Supabase, and STUN comes from public servers.
1. Clone and install
git clone https://github.com/biioshq/teleprompt.git
cd teleprompt
npm install
cp .env.example .env2. Supabase
- Create a project at supabase.com. Keep the database password you set.
- Go to Project Settings → API and copy the Project URL and the publishable / anon key. Both are public by design; they go into
NEXT_PUBLIC_variables and ship to the browser. - Go to Project Settings → Database and copy the Transaction pooler connection string. Read the note below before reaching for the direct one.
Use a pooler, not the direct endpoint
db.<ref>.supabase.co endpoint resolves to an IPv6 address only - it has no A record at all. Hosts without IPv6 egress, which includes Vercel’s functions, simply cannot reach it.Nothing in the resulting error mentions the database. It arrives as a generic sign-in misconfiguration, in production only, with the same environment variables that work locally. Deployment troubleshooting covers confirming that is what you are looking at.
So use a pooler URI. Two things differ from the direct string: the host becomes
aws-0-<region>.pooler.supabase.com, and the username becomes postgres.<project-ref> rather than plain postgres. Port 6543 is the transaction pooler, which suits serverless; port 5432 is the session pooler, which behaves like a direct connection. The app detects the transaction pooler and turns off prepared statements by itself. Append ?sslmode=require either way.Nothing else needs configuring in Supabase. Teleprompt does not use Supabase Auth or Storage, and it never reads a table from the browser, so there are no row-level security policies to write. Realtime broadcast works with the anon key out of the box, and access control is the room’s 256-bit channel key. See Architecture.
3. Sign-in providers
Teleprompt supports Google and GitHub. Configure one, the other, or both: the sign-in page shows whichever have credentials and names the variables for whichever do not. You need at least one.
- Open the Google Cloud credentials console and create an OAuth client ID of type Web application.
- Add authorised redirect URIs. One per origin you will use:
http://localhost:3000/api/auth/callback/google https://your-domain.example/api/auth/callback/google - On the OAuth consent screen, the only scopes needed are the default email and profile scopes.
- Copy the client ID and client secret into
AUTH_GOOGLE_IDandAUTH_GOOGLE_SECRET.
GitHub
- Go to Settings → Developer settings → OAuth Apps and choose New OAuth App.
- Set the homepage URL to your origin and the Authorization callback URL to:
A GitHub OAuth App accepts only one callback URL, so development and production need separate apps. This is the main way it differs from Google, which takes a list.http://localhost:3000/api/auth/callback/github - Generate a client secret, then copy both into
AUTH_GITHUB_IDandAUTH_GITHUB_SECRET.
One person, one account
Teleprompt therefore links accounts that share an email address, and only ever accepts an address the provider says is verified. Auth.js’s stock GitHub provider takes the primary address without checking that flag; Teleprompt overrides it so an unverified address cannot be used to reach an existing account.
4. Environment
Fill in .env. Generate the auth secret with npx auth secret, or any 32 random bytes in base64.
AUTH_SECRET="…"
AUTH_TRUST_HOST="true"
# At least one provider.
AUTH_GOOGLE_ID="…apps.googleusercontent.com"
AUTH_GOOGLE_SECRET="…"
AUTH_GITHUB_ID="…"
AUTH_GITHUB_SECRET="…"
DATABASE_URL="postgresql://postgres.REF:PASSWORD@aws-0-REGION.pooler.supabase.com:6543/postgres?sslmode=require"
NEXT_PUBLIC_SUPABASE_URL="https://REF.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="…"
# Only off Vercel; see below. Bare hostname, no scheme.
# NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="teleprompt.example"AUTH_TRUST_HOST is not optional outside Vercel. Without it Auth.js refuses to build callback URLs from the incoming Host header and sign-in fails with UntrustedHost.
5. Push the schema
npm run db:pushThis creates seven tables, all prefixed teleprompt_, so the project can share a Supabase database with something else without colliding. npm run db:studio opens Drizzle Studio if you want to look at them.
The app connects over the transaction pooler on port 6543, but the Drizzle CLI needs one connection for a whole run, so drizzle.config.ts swaps :6543 for :5432 on .pooler.supabase.com hosts by itself. There is nothing to configure for a standard Supabase setup, and a direct db.<ref>.supabase.co host or a local Postgres is already on 5432 and is left alone. If your setup does not match that substitution, set DIRECT_DATABASE_URL to a session-mode connection and it is used instead.
If the push stops with TypeError: Cannot read properties of undefined (reading ‘replace’) somewhere inside drizzle-kit, the connection is the problem rather than your schema; Deployment troubleshooting explains why and what to do about it.
6. Run it
npm run dev # http://localhost:3000
npm run typecheck # no emit, strict
npm run build # production build
npm run preview # build, then serve itThe service worker is only registered in production builds, so use npm run preview to test installing the app.
Deploying
Where the public origin comes from
NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL, a Vercel system variable that is populated automatically and, usefully, always points at production even from a preview deployment, so a preview never advertises itself as the real site. The value carries no scheme, so the app adds https:// itself.It is a standard Next.js App Router application with no edge-only or platform-specific code, so anywhere that runs Next works: Vercel, Netlify, Fly, Railway, a container on your own box.
- Set every variable above in the host’s environment.
- On Vercel,
NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URLis filled in for you, provided you leave Enable access to System Environment Variables switched on. - Anywhere else, set that same variable by hand to your own domain. It wants a bare hostname:
teleprompt.example, nothttps://teleprompt.example/. Leave it unset and the app falls back to localhost, which will put localhost links in your sitemap and Open Graph tags. - Add the production callback URL to every provider before you try to sign in. Google takes a list of redirect URIs; GitHub allows one per OAuth App, so production needs its own app.
- Serve over HTTPS. WebRTC, the service worker and the wake lock all require a secure context.
For the things that only go wrong once it is deployed, such as sign-in that works on your machine and not on the host, or a database the deployment cannot reach, see Deployment troubleshooting, which starts at /api/health.
Variable reference
| Variable | Required | Notes |
|---|---|---|
AUTH_SECRET | In production | Signs the session cookie. |
AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET | One provider | Optional at build time; the sign-in page names whatever is missing. |
AUTH_GITHUB_ID, AUTH_GITHUB_SECRET | One provider | As above. At least one of the two pairs must be set. |
AUTH_TRUST_HOST | Off Vercel | Set to true. |
DATABASE_URL | Yes | Postgres. Use a pooler host, not the IPv6-only direct endpoint, and add ?sslmode=require. |
NEXT_PUBLIC_SUPABASE_URL | Yes | Realtime endpoint. Public. |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Yes | Publishable key. Public. |
NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL | Off Vercel | Bare hostname, no scheme. Automatic on Vercel, and always the production domain even on a preview. |
SKIP_ENV_VALIDATION | No | Escape hatch for container builds. |