Skip to main content
vibestrap separates configuration into three layers, each with a clear purpose. Knowing which layer to touch will save you a lot of time.

The three layers

LayerFileWhat it controls
Brand & business configsrc/config/site.tsProduct name, pricing, plans, providers active flag, social links — anything a buyer should change.
Secrets & runtime config.env.local (validated by src/env.ts)API keys, database URL, signing secrets — anything that varies per environment.
Customer-facing copymessages/en.json + messages/zh.jsonEvery string the user sees. Bilingual; both files must have the same keys.
If you find yourself editing component source to change copy, stop — you should be editing messages/{en,zh}.json instead.

What siteConfig controls

The whole file is ~250 lines. Here are the load-bearing fields:
siteConfig.name            // brand name, used in <title> + emails + footer
siteConfig.url             // canonical URL — drives sitemap, OG, mail links
siteConfig.shortDescription // hero subtitle, OG image
siteConfig.product         // pricing card: standard / promo, price IDs per provider
siteConfig.payment.provider // 'stripe' | 'paddle' | 'lemonsqueezy' | 'creem'
siteConfig.customerService  // { enable, provider: 'crisp' | 'tawk' | … }
siteConfig.affiliate        // { enable, provider, internalCommissionPct }
siteConfig.newsletter       // { enable, provider: 'resend' | 'beehiiv' }
siteConfig.analytics        // { vercel, googleAnalytics, posthog, plausible, umami }
siteConfig.credits          // ledger config: registerGift, monthlyFree, …
siteConfig.plans            // free / pro / lifetime — priceCents + monthlyCredits
siteConfig.creditPacks      // basic / standard / premium / enterprise
siteConfig.i18n             // locales + default locale
siteConfig.features         // feature flags (blog / newsletter / OAuth providers)
Every field has inline comments — read it once cover-to-cover, you’ll know what you can change without grep-ing the codebase.

What lives outside siteConfig

These have their own homes — by design.
ConcernWhere
Env vars / secrets.env.local, validated by src/env.ts
Database schemasrc/db/{auth,app,affiliate,ai,license}.schema.ts
AI provider list / pricingsrc/ai/index.ts + src/ai/pricing.ts
Marketing copymessages/{en,zh}.json
Mail templatessrc/mail/templates/
Theme tokenssrc/app/globals.css (@theme block)
Sidebar nav for /docssrc/config/docs-nav.ts

Adding a new feature flag

  1. Add to siteConfig.features:
    features: { ..., enableMyFeature: true }
    
  2. Reference in code:
    import { siteConfig } from '@/config/site';
    if (siteConfig.features.enableMyFeature) { ... }
    
That’s it — no extra plumbing.

Adding an env var

  1. Add a Zod-validated entry in src/env.ts (server section if it’s secret; client section if it has the NEXT_PUBLIC_ prefix and is safe to ship to the browser).
  2. If it’s a public var, add it to experimental__runtimeEnv too (Next.js requires this).
  3. Document it in Env reference.
  4. Use import { env } from '@/env' to read it — never process.env.X directly.

Best-practice checklist

  • ✅ Brand changes happen in siteConfig and messages/. No component-source edits.
  • ✅ Secrets only in .env.local. The .env.example documents the shape; the real .env.local is gitignored.
  • ✅ When swapping a provider (Stripe → Paddle), change siteConfig.payment.provider AND set the matching *_PRICE_* env vars. The rest of the app doesn’t care which provider is active.
  • ✅ Both messages/en.json and messages/zh.json always have the same keys. node scripts/check-i18n.mjs enforces this.