Fixing Stripe Checkout Redirects: Setting NEXT_PUBLIC_APP_URL
Fixing Stripe Checkout Redirects: Setting NEXT_PUBLIC_APP_URL
Affected versions: all versions prior to v1.0.21
File:src/lib/routers/billing.ts
What Happened
The billing router builds Stripe checkout session redirect URLs by interpolating process.env.NEXT_PUBLIC_APP_URL:
// billing.ts line 47
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard/billing?success=true`,
// billing.ts line 73
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard/billing?cancelled=true`,
Because NEXT_PUBLIC_APP_URL was never defined in the project's environment configuration, both values resolved to undefined at runtime. Stripe rejects checkout session requests that contain invalid redirect URLs, so any attempt to initiate a billing flow would fail silently or throw a Stripe API error.
How to Fix It
Step 1 — Set the environment variable
Add NEXT_PUBLIC_APP_URL to every environment where the application runs.
Local development (.env.local):
NEXT_PUBLIC_APP_URL=http://localhost:3000
Vercel (Production & Preview):
- Open your project in the Vercel dashboard.
- Go to Settings → Environment Variables.
- Add a new variable:
- Name:
NEXT_PUBLIC_APP_URL - Value:
https://your-domain.com(use your actual production domain) - Environments: ✅ Production ✅ Preview ✅ Development
- Name:
- Redeploy the application for the change to take effect.
Step 2 (Optional) — Apply the resilient fallback
For teams using Vercel preview deployments, the URL changes with every branch deploy. Instead of manually updating NEXT_PUBLIC_APP_URL for each preview, update billing.ts to fall back to Vercel's automatically injected VERCEL_URL:
const base =
process.env.NEXT_PUBLIC_APP_URL ??
(process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: 'http://localhost:3000');
// Then use `base` in your session config:
const session = await stripe.checkout.sessions.create({
// ...
success_url: `${base}/dashboard/billing?success=true`,
cancel_url: `${base}/dashboard/billing?cancelled=true`,
});
Important: Vercel sets
VERCEL_URLto the hostname only (e.g.my-app-abc123.vercel.app), without a protocol. Always prependhttps://as shown above.
Priority Resolution Order
| Priority | Variable | Example value |
|---|---|---|
| 1 (highest) | NEXT_PUBLIC_APP_URL | https://app.mygym.com |
| 2 | VERCEL_URL | https://my-app-abc123.vercel.app |
| 3 (fallback) | hardcoded localhost | http://localhost:3000 |
Why NEXT_PUBLIC_ Prefix Matters
Next.js only exposes environment variables to the browser bundle when they are prefixed with NEXT_PUBLIC_. Because Stripe checkout URLs are constructed server-side in a tRPC router, the prefix is not strictly required for this use case — but using it ensures the same base URL is also accessible in client-side code if needed elsewhere (e.g. for constructing absolute links in emails or webhooks).
Verification
After setting the variable and redeploying:
- Open the billing page in your application.
- Initiate a test checkout using a Stripe test card.
- Confirm you are redirected to
/dashboard/billing?success=trueon completion. - Confirm you are redirected to
/dashboard/billing?cancelled=trueon cancellation.
If either redirect lands on a Stripe error page or a 404, double-check that the environment variable is set correctly and that the deployment has been rebuilt after the change.