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.svgandpublic/favicon.svgwith 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 inapp/globals.css(the default--primaryis a monochromeoklch(0.205 0 0)). To rebrand, update--primary,--accent,--secondary, and the related tokens to your ownoklch()values. Tailwind and shadcn read these via the@theme inlineblock, so the whole UI follows automatically.
2. Business model: B2C or B2B?
Edit config/app.ts:
export const appConfig = {
// ...
businessModel: 'b2c', // or 'b2b'
}
| B2C | B2B | |
|---|---|---|
| Account model | 1 user = 1 personal account | N users = 1 workspace |
| Onboarding | Single signup | Signup + workspace setup + invitations |
| Pricing | Per user | Per workspace (with seats) |
| Sidebar | Personal dashboard only | Personal + org dashboards |
You can switch later, but existing accounts stay as they were created.
3. Billing model
Also in config/app.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 whatsubscriptions.plan_idstores) - a
name - an amount in whole currency units (e.g.
29for €29) - a
stripePriceIdfor each billing period
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:
- Edit
i18n/config.ts: update thelocalesarray,localeNames,localeFlags. - Copy
i18n/messages/en-US.jsonto your new locale (e.g.es-ES.json) and translate the values. - Restart the dev server.
Full reference: Internationalization.
6. Email provider
If you picked noop earlier, switch to Brevo or Mailjet now:
- Open the provider dashboard → API keys → copy your key.
- Paste it in
.env.localasBREVO_API_KEYorMAILJET_API_KEY_PUBLIC+MAILJET_API_KEY_PRIVATE. - Set
EMAIL_FROM_NAMEandEMAIL_FROM_ADDRESS. - Verify your sender domain inside the provider (SPF + DKIM). Magic links land in spam without this.
- Test by requesting a magic link from
/en-US/loginwith 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 editi18n/messages/{locale}.json. - Logo cloud — drop SVGs in
public/logos/and reference them incomponents/logo-cloud.tsx. - Legal pages —
/terms,/privacy,/legalare CMS pages. Edit them in/admin-dashboard/cmsonce logged in as admin.
- 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