1. Branding

The init wizard wrote your app name, URL, and color into config/app.ts. Open that file now and review every field. Anything that says "PLACEHOLDER", your-domain.com, or stays as default needs to be replaced.

  • Logo — replace public/icon.svg and public/favicon.svg with your own. The favicon shows in browser tabs; the icon is used in OG cards and the dashboard.
  • OG image — replace public/images/opengraph.png (1200×630).
  • Brand color — the project uses oklch() CSS variables defined in app/globals.css (the default --primary is a monochrome oklch(0.205 0 0)). To rebrand, update --primary, --accent, --secondary, and the related tokens to your own oklch() values. Tailwind and shadcn read these via the @theme inline block, so the whole UI follows automatically.

2. Business model: B2C or B2B?

Edit config/app.ts:

ts
export const appConfig = {
  // ...
  businessModel: 'b2c', // or 'b2b'
}
B2CB2B
Account model1 user = 1 personal accountN users = 1 workspace
OnboardingSingle signupSignup + workspace setup + invitations
PricingPer userPer workspace (with seats)
SidebarPersonal dashboard onlyPersonal + org dashboards

You can switch later, but existing accounts stay as they were created.

3. Billing model

Also in config/app.ts:

ts
billingModel: 'subscription' // or 'license' or 'hybrid'
  • subscription — recurring payments through Stripe. Default for SaaS.
  • license — one-time payment. The user gets lifetime access to a product. Good for code/templates/digital goods.
  • hybrid — both. Useful if you sell both a subscription tier and one-off add-ons.

The pricing page automatically shows only the purchase types your model enables.

4. Finalize your prices

Open config/pricing.ts and shape your plans / credit packs / licenses. Each plan has:

  • an id (kept as-is forever — it is what subscriptions.plan_id stores)
  • a name
  • an amount in whole currency units (e.g. 29 for €29)
  • a stripePriceId for each billing period
Test IDs vs live IDs

The same config/pricing.ts file is used in dev and prod. The stripePriceIds map at the top of the file is keyed by plan → interval → currency → { dev, prod }, and the getPriceId(planId, currency, interval) helper picks the right one based on NEXT_PUBLIC_INSTANCE_MODE. Fill in the dev field with your test-mode price_... ID and the prod field with the live one — no code changes needed to swap between modes.

5. Languages (i18n)

The boilerplate ships with French (fr-FR), Swiss French (fr-CH), US English (en-US), and Canadian English (en-CA), URL-based: /fr-FR/..., /fr-CH/..., /en-US/..., and /en-CA/.... To remove one or add one:

  1. Edit i18n/config.ts: update the locales array, localeNames, localeFlags.
  2. Copy i18n/messages/en-US.json to your new locale (e.g. es-ES.json) and translate the values.
  3. Restart the dev server.

Full reference: Internationalization.

6. Email provider

If you picked noop earlier, switch to Brevo or Mailjet now:

  1. Open the provider dashboard → API keys → copy your key.
  2. Paste it in .env.local as BREVO_API_KEY or MAILJET_API_KEY_PUBLIC + MAILJET_API_KEY_PRIVATE.
  3. Set EMAIL_FROM_NAME and EMAIL_FROM_ADDRESS.
  4. Verify your sender domain inside the provider (SPF + DKIM). Magic links land in spam without this.
  5. Test by requesting a magic link from /en-US/login with a real email address.

7. Replace the placeholder content

  • Hero, features, testimonials, FAQ — landing components live in components/landing-*.tsx. Most read content from i18n message files, so edit i18n/messages/{locale}.json.
  • Logo cloud — drop SVGs in public/logos/ and reference them in components/logo-cloud.tsx.
  • Legal pages/terms, /privacy, /legal are CMS pages. Edit them in /admin-dashboard/cms once logged in as admin.
You can move on when…
  • The landing page shows your real branding, real prices, no placeholders
  • Your locales array matches what you actually want to sell in
  • A magic link sent to a real inbox arrives within ~30 seconds, not in spam
  • Legal pages render with your company info, not Lorem Ipsum