Mental model
vibestrap follows a fill-what-you-use model. The app boots with two real values (DATABASE_URL + BETTER_AUTH_SECRET); everything else is optional and gracefully
no-ops when blank. Don’t have a Stripe account yet? The pricing page just hides the
checkout button. No Resend key? Password reset still renders, just doesn’t send.
But here’s the trap most people fall into:
The 3 config layers
vibestrap’s configuration lives in three places, each for a reason:| Layer | Lives in | What it controls |
|---|---|---|
| Brand & product | src/config/site.ts | Name, copy, pricing display, social links |
| Translations | messages/{en,zh}.json | All user-facing strings |
| Secrets & toggles | k8s/.env (or .env.local for dev) | API keys, DB URLs, provider switches |
Where you’ll edit values
The flow on a live cluster:.env.local and pnpm dev picks them up
automatically — no restart dance.
Required: the 4 values without which the site shows 500
Skip any of these and the site won’t render. Do these first.DATABASE_URL — your Postgres
Why: every page that reads or writes data goes through this. Without a real DB,
every non-static route returns 500. The marketing homepage might load;
/login,
/register, /pricing, /admin will not.Where to sign up: neon.tech — serverless Postgres, generous
free tier, indie-friendly. (Alternatives: Supabase, Railway, RDS — anything that
speaks Postgres 15+.)Create the project
- Sign up at https://neon.tech
- Create a new project — pick a region close to your K8s cluster (e.g.
us-east-2if your nodes are there) - Wait ~10 seconds for provisioning to finish
Grab the connection string
- In the left sidebar, click Connection Details
- Important: select Pooled connection (handles serverless and burst traffic way better than direct)
- Copy the URL it shows
- Make sure it ends with
?sslmode=require— Neon enforces TLS, your app will fail to connect without it
| Variable | Example |
|---|---|
DATABASE_URL | postgres://neondb_owner:[email protected]/neondb?sslmode=require |
BETTER_AUTH_SECRET — auth signing key
Why: signs session cookies and JWT tokens. If it’s left as the placeholder, anyone
who reads the public template repo can forge sessions for any user on your site. This
is a security hole that would end your business on day one.Where to sign up: nowhere — generate it on your laptop.
| Variable | Example |
|---|---|
BETTER_AUTH_SECRET | S8sFO3lhBaqfDD0p308BqjBNQ6TssHA9i2p4JoMYSb4= |
NEXT_PUBLIC_APP_URL — your domain
Why: this value gets baked into the client bundle at build time. It’s used for
OG images, sitemap URLs, OAuth redirect URIs, and links inside emails. If it points at
localhost, every shared link will be broken.| Variable | Example |
|---|---|
NEXT_PUBLIC_APP_URL | https://vibestrap.dev |
Must be the production https URL. Don’t include a trailing slash. Don’t use
http://.BETTER_AUTH_URL — auth callback root
Why: Better Auth’s OAuth callback signature relies on this. If wrong, sign-in
callbacks reject as “invalid origin” and users see a confused error page.In 99% of cases this should be the same as
NEXT_PUBLIC_APP_URL. The only reason to
split them is if your auth lives on a different subdomain.| Variable | Example |
|---|---|
BETTER_AUTH_URL | https://vibestrap.dev |
Strongly recommended: the things buyers expect to work
Without these, the site loads but key features are broken: users can’t recover passwords, can’t sign in via Google or GitHub, can’t pay you. Set them up before launch — not after the first user complaint.Resend (transactional email)
Why: password reset, email verification, welcome emails. Without it, the “forgot password” button is purely decorative. Where to sign up: resend.com — free tier covers 100 emails per day, 3,000 per month. Plenty for early launch.Add and verify your domain
- Left sidebar → Domains → Add Domain
- Enter your domain (e.g.
vibestrap.dev) - Resend shows 3 DNS records (SPF, DKIM, DMARC) — don’t close the page
- Add those DNS records at your registrar (Cloudflare, Namecheap, GoDaddy, …)
- Click Verify DNS Records; wait 5–30 min until status shows Verified
| Variable | Where it comes from | Example |
|---|---|---|
RESEND_API_KEY | Resend → API Keys | re_a1b2c3d4... |
RESEND_FROM_EMAIL | Any email at your verified domain | Larry from Vibestrap <[email protected]> |
RESEND_REPLY_TO_EMAIL | Inbox you actually read | [email protected] |
The human-readable name in
RESEND_FROM_EMAIL (e.g. Larry from Vibestrap) is
optional, but it noticeably improves deliverability — bare hello@… looks like a bot.Admin access
You’ll want to reach/admin yourself.
| Variable | Example |
|---|---|
ADMIN_EMAILS | [email protected],[email protected] |
admin role on first login and from then on can access /admin.
Google OAuth
Why: roughly 60% of indie devs prefer “sign in with Google” over typing a password. The conversion lift on a signup form with this button alone is huge. Where to set up: console.cloud.google.comConfigure the OAuth consent screen
- Left menu → APIs & Services → OAuth consent screen
- User type: External → Create
- Fill App name, User support email, Developer contact email
- Save and continue through the scopes / test users screens (defaults are fine)
Create the OAuth client
- Left menu → APIs & Services → Credentials
- + Create Credentials → OAuth 2.0 Client ID
- Application type: Web application
- Name: anything (e.g. “vibestrap”)
- Authorized JavaScript origins:
https://vibestrap.dev - Authorized redirect URIs:
https://vibestrap.dev/api/auth/callback/google(this exact path — Better Auth uses it) - Click Create → modal shows Client ID + Client Secret → copy both immediately
| Variable | Example |
|---|---|
GOOGLE_CLIENT_ID | 1234567890-abc...apps.googleusercontent.com |
GOOGLE_CLIENT_SECRET | GOCSPX-abcdef... |
NEXT_PUBLIC_GOOGLE_CLIENT_ID | same as GOOGLE_CLIENT_ID (mirror, used for client-side One-Tap) |
GitHub OAuth
Why: vibestrap targets developers, and “sign in with GitHub” converts ~80% of devs who land on your signup page. Worth 5 minutes of setup. Where to set up: github.com/settings/developers → OAuth Apps → New OAuth AppFill the form
- Application name: vibestrap (or whatever you want users to see)
- Homepage URL:
https://vibestrap.dev - Authorization callback URL:
https://vibestrap.dev/api/auth/callback/github - Click Register application
| Variable | Example |
|---|---|
GITHUB_CLIENT_ID | Iv1.abc123... |
GITHUB_CLIENT_SECRET | ghs_abcdef... |
Stripe
Why: take money. Without this, the entire pricing page is decorative. Where to set up: dashboard.stripe.comActivate your Stripe account
Provide tax + banking info. Required before Stripe gives you live keys. Test mode
works without activation, but you can’t actually receive payouts.
Grab the API keys
- Developers → API keys
- Copy Secret key (
sk_live_...) - Copy Publishable key (
pk_live_...)
Create the product and prices
- Products → + Add product
- Name it “vibestrap” (or whatever your product is called)
- Add prices under that product. For vibestrap typically two:
- Standard one-time: e.g. $99
- Promo one-time: e.g. $49 (optional sale price)
- Click into each price; copy the
price_xxxID from the URL or the price detail panel
Register the webhook
- Developers → Webhooks → + Add endpoint
- Endpoint URL:
https://vibestrap.dev/api/webhooks/stripe - Events to send (toggle each):
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failed
- Click Add endpoint
- On the endpoint detail page, click Reveal next to Signing secret → copy
whsec_...
| Variable | Example |
|---|---|
STRIPE_SECRET_KEY | sk_live_abc... |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | pk_live_abc... |
STRIPE_WEBHOOK_SECRET | whsec_abc... |
STRIPE_PRICE_VIBESTRAP_STANDARD | price_abc... (your standard SKU) |
STRIPE_PRICE_VIBESTRAP_PROMO | price_abc... (optional, for sale price) |
SEO + analytics — strongly recommended
These don’t affect whether your site works, but they directly affect whether anyone finds it.Google Search Console — site verification
Why: Google won’t reliably index your site without verification. No SEO traffic = no organic growth = paid ads are your only acquisition channel. Where: search.google.com/search-consoleAdd the property
- Add property → URL prefix → enter
https://vibestrap.dev - Choose HTML tag verification method
| Variable | Example |
|---|---|
GOOGLE_SITE_VERIFICATION | Rj7vQ... (the content="..." value, not the full tag) |
Microsoft Clarity — heatmaps + session recordings
Why: free heatmaps, session recordings, and dead-click detection. The best $0 you’ll ever not-spend on user research. Where: clarity.microsoft.com| Variable | Example |
|---|---|
NEXT_PUBLIC_CLARITY_PROJECT_ID | abc123xyz0 |
PostHog — product analytics (optional)
Why: funnels, retention, feature flags. Free tier covers 1M events/month, more than enough for early-stage products. Where: posthog.com| Variable | Example |
|---|---|
NEXT_PUBLIC_POSTHOG_KEY | phc_abc... |
NEXT_PUBLIC_POSTHOG_HOST | https://us.i.posthog.com (default) |
Optional — only when you actually need them
A single table for everything else. Don’t pre-emptively wire these — they’re each “feature off until you fill in the var”.| Feature | Set up when | Variables | Where to sign up |
|---|---|---|---|
| Bing site verification | You want Bing/DuckDuckGo traffic | BING_SITE_VERIFICATION | bing.com/webmasters |
| Yandex verification | Targeting Russian-speaking users | YANDEX_VERIFICATION | webmaster.yandex.com |
| Resend Audience newsletter | Collecting email subs via Resend | RESEND_AUDIENCE_ID | resend.com → Audiences |
| Beehiiv newsletter | You already use Beehiiv | BEEHIIV_API_KEY, BEEHIIV_PUBLICATION_ID | beehiiv.com |
| Crisp chat | Want live chat widget | NEXT_PUBLIC_CRISP_WEBSITE_ID | crisp.chat |
| Tawk.to chat | Free Crisp alternative | NEXT_PUBLIC_TAWK_PROPERTY_ID, NEXT_PUBLIC_TAWK_WIDGET_ID | tawk.to |
| Intercom | Bigger budget, better CRM | NEXT_PUBLIC_INTERCOM_APP_ID | intercom.com |
| Chatwoot | Self-hosted support | NEXT_PUBLIC_CHATWOOT_WEBSITE_TOKEN, NEXT_PUBLIC_CHATWOOT_BASE_URL | chatwoot.com |
| Cloudflare Turnstile | Anti-bot on signup/checkout | NEXT_PUBLIC_TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY | cloudflare.com |
| Affonso affiliates | Affiliate program | NEXT_PUBLIC_AFFONSO_ID | affonso.io |
| Rewardful affiliates | Stripe-native affiliates | NEXT_PUBLIC_REWARDFUL_API_KEY | rewardful.com |
| Tolt affiliates | Cheaper alternative | NEXT_PUBLIC_TOLT_REFERRAL_KEY | tolt.io |
| Creem (alt to Stripe) | Stripe unavailable in your region | CREEM_API_KEY, CREEM_WEBHOOK_SECRET, CREEM_PRODUCT_ID | creem.io |
| AI providers (Phase 2) | Your product uses AI | AI_PROVIDER, OPENAI_API_KEY, ANTHROPIC_API_KEY, OPENROUTER_API_KEY, REPLICATE_API_TOKEN, FAL_KEY | provider websites |
| S3 / R2 storage | User uploads, generated images | S3_ENDPOINT, S3_REGION, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY | AWS S3 or Cloudflare R2 |
| License download URL | Where buyers fetch the zip | LICENSE_DOWNLOAD_URL | your private GitHub release / S3 link |
Apply + verify
You’ve filled ink8s/.env. Now push it into the cluster and prove it works.
Apply your changes
Verify the user flows (don’t trust pod status alone)
kubectl get pods showing 1/1 Running only proves the process boots. It does not
prove the database is reachable, OAuth redirects work, or Stripe webhooks fire. Walk
through this list manually:
Homepage renders
Open
https://vibestrap.dev. Should render. If it 500s, the most common causes are
typoed DATABASE_URL, or you forgot to run pnpm db:migrate after
configuring the database (see the warning at the top of the
“Required” section).Email signup works
Go to
/register, sign up with email. Check your inbox for the welcome email.
Validates: Resend API key + verified domain.Google sign-in works
Go to
/login, click Continue with Google. Should redirect, prompt, return to
your app logged in. Validates: Google OAuth client + redirect URI.Forgot-password email arrives
On
/login, click “Forgot password”. Submit your email. Email should arrive within
30 seconds. Validates: Resend further + auth wiring.Admin dashboard reachable
Sign in with an email matching
ADMIN_EMAILS. Visit /admin. Should load — not 403.
Validates: ADMIN_EMAILS parsing.Pricing page → Stripe Checkout
Visit
/pricing. Click any checkout button. Should land on a Stripe Checkout page
under checkout.stripe.com. Validates: Stripe live keys + price IDs are configured.A real test purchase end-to-end
On the Stripe Checkout page, use Stripe’s test card
4242 4242 4242 4242 (only works
in test mode — for live mode use a real card and refund yourself, or run this whole
test against a sandbox first). Confirm:- payment shows up in Stripe dashboard
- webhook fires (Stripe → Webhooks → your endpoint shows a
200) - a row appears in
paymenttable - a license appears in
/admin/licenses(or/settings/licensesfor your account)
Configuration reference
Every config layer in detail.
Env reference
The full list of every env var vibestrap reads.
Stripe deep dive
More Stripe specifics: subscriptions, refunds, dispute handling.
Kubernetes deployment
The cluster setup, Harbor registry, secret rotation.