Customization
Branding, theming, feature flags, i18n, analytics, SEO, pricing — all configurable from centralized files. Zero architectural changes.
Branding (White-Label)
All brand identity in lib/config.ts. Change once → propagates to 50+ files:
export const config = {
app: {
name: "Your SaaS Name",
shortName: "YSN",
description: "Your tagline",
url: process.env.NEXT_PUBLIC_APP_URL,
author: { name: "Your Company", url: "https://yourcompany.com" },
legal: { companyName: "Your Company LLC", email: "[email protected]" },
links: { twitter: "...", github: "..." },
},
};Propagates to: header, footer, emails, SEO, legal, AI chat prompt, OpenAPI docs.
Checklist
| Item | File | Change |
|---|---|---|
| Name + tagline | lib/config.ts | config.app.name, config.app.description |
| Logo | public/ | Replace logo.svg, favicon.ico, og-image.png |
| Fonts | app/layout.tsx | Import via next/font/google |
| Colors | app/globals.css | OKLCH tokens (:root + .dark) |
| Email sender | .env.local | EMAIL_FROM, EMAIL_FROM_NAME |
| Social links | components/global/footer.tsx | Footer URLs |
| Legal | /super-admin/legal | AI Legal Generator |
| OG image | public/og-image.png | Social sharing preview |
Never hardcode app names. Always
config.app.name.
Theming (Colors & Dark Mode)
OKLCH tokens in app/globals.css:
:root {
--background: oklch(0.985 0.005 260);
--foreground: oklch(0.2 0.02 260);
--primary: oklch(46.5% 0.24 302);
}
.dark {
--background: oklch(0.18 0.04 260);
--foreground: oklch(0.98 0.01 260);
--primary: oklch(56% 0.29 301);
}- Edit OKLCH values
- Keep
:root+.darkin sync - Semantic tokens only (
bg-primary,text-foreground) — never raw colors
Dark mode via next-themes + ThemeProvider.
Feature Flags
lib/config.ts:
features: {
organizations: true, // Multi-tenancy
projects: true, // Project management
i18n: true, // Internationalization
waitlist: false, // Pre-launch mode
affiliate: true, // Affiliate program
aiChat: true, // AI widget
blog: true, // Blog system
}Gates both UI + server-side:
// UI
{isOrganizationsEnabled() && <OrgSwitcher />}
// Server
if (!isOrganizationsEnabled()) return { success: false, error: t("disabled") };Helpers
Import feature gates from @/lib/config/app; do not read config.features.* directly in application code.
isProjectsEnabled() · isOrganizationsEnabled() · isCreditsEnabled() · isSeatsEnabled() · isSubscriptionsEnabled() · isLifetimePurchasesEnabled() · isDigitalProductsEnabled() · isTrialEnabled() · isBlogEnabled() · isAiChatEnabled() · isVoiceEnabled() · isSTTEnabled() · isWaitlistEnabled() · isI18nEnabled() · isAnalyticsEnabled() · isStorageEnabled() · isEmailEnabled() · isAffiliatesEnabled() · isSupportTicketsEnabled() · isDocsEnabled() · isApiReferenceEnabled()
Dependencies
- Organizations and Projects are fully decoupled
- Seat billing requires orgs.
isSeatsEnabled()auto-returnsfalsewhen orgs disabled
Internationalization (i18n)
| Property | Value |
|---|---|
| Library | next-intl |
| Locales | en, es, fr |
| Gate | isI18nEnabled() |
| Strategy | Cookie-based (no URL prefix) |
| Sub-modules | 14 domains, 40+ namespaces |
| Globals | company, app_name via i18n.ts |
Full ref: .agents/skills/i18n/SKILL.md.
// Client
import { useTranslations } from "next-intl";
const t = useTranslations("dashboard");
// Server
import { getTranslations } from "next-intl/server";
const t = await getTranslations("dashboard");Global Translation Values
Global variables like company and app_name are injected via i18n.ts using getRequestConfig(). Available in all components without prop drilling:
// In translation files:
{ "welcome": "Welcome to {app_name}" }
// Automatically resolved — no extra props neededDo NOT pass
defaultTranslationValuesas a prop toNextIntlClientProvider. That prop was removed in next-intl v4. Usei18n.tsinstead.
Validation
| Script | Command | Detects |
|---|---|---|
| Missing keys | npx tsx scripts/find-missing-locale-keys.ts | Used-but-undefined keys |
| Missing translations | npx tsx scripts/find-missing-translations.ts | Cross-locale gaps |
Run both after modifying keys.
Analytics (Universal Adapter)
One env var:
NEXT_EXTERNAL_ANALYTICS_PROVIDER=datafast| Provider | Env Var(s) | Integration |
|---|---|---|
| DataFast | NEXT_PUBLIC_DATAFAST_WEBSITE_ID + NEXT_PUBLIC_DATAFAST_DOMAIN | CDN |
| GA4 | NEXT_PUBLIC_GA_MEASUREMENT_ID | CDN |
| Mixpanel | NEXT_PUBLIC_MIXPANEL_TOKEN | NPM |
| PostHog | NEXT_PUBLIC_POSTHOG_KEY + HOST | NPM |
| Plausible | NEXT_PUBLIC_PLAUSIBLE_DOMAIN | CDN |
| Pirsch | NEXT_PUBLIC_PIRSCH_CODE | CDN |
| Umami | NEXT_PUBLIC_UMAMI_WEBSITE_ID + HOST | CDN |
| Vercel | — (auto) | NPM |
| Vemetric | NEXT_PUBLIC_VEMETRIC_TOKEN | NPM |
<AnalyticsProvider /> in app/layout.tsx handles injection. It does not mount third-party scripts or emit page events until lib/analytics/consent.ts records explicit analytics consent. DataFast also requires NEXT_PUBLIC_DATAFAST_DOMAIN; Sentry Session Replay remains disabled unless NEXT_PUBLIC_SENTRY_REPLAY_ENABLED=true.
SEO
| Function | Use Case |
|---|---|
generatePageMetadata() | Standard pages |
generateArticleMetadata() | Blog posts |
generateOrganizationSchema() | Landing JSON-LD |
generateProductSchema() | Pricing JSON-LD |
import { generatePageMetadata } from "@/lib/seo";
export const metadata = generatePageMetadata({
title: "Dashboard","
description: "Your performance overview.",
path: "/dashboard",
noIndex: true,
});Pricing Tiers
Single source of truth: lib/config/pricing.ts:
| Tier | Price | Key Limits |
|---|---|---|
| 🔥 Free | $0/mo | 3 projects, 50 AI credits, 100MB |
| ⭐ Starter | $29/mo | Unlimited projects, advanced AI, orgs |
| 💎 Pro | $49/mo | Everything + voice, priority support, unlimited AI |
Customizing
| Operation | Steps |
|---|---|
| Add tier | Add key to PRICING_TIERS, set prices (cents), limits, provider env vars |
| Change prices | Update cents + create new entries in provider dashboard + update env vars |
| Rename | Change name only. Never the key — stored in profiles.subscription_tier |
| Remove | isAvailable: false to grandfather subs |
| Change limits | Edit limits/credits/rateLimit — server-side automatic |
Rules: One isDefault: true. id matches key. Prices in cents. Provider IDs from env vars.
See PAYMENTS.md for full adapter + webhook reference.
Copyright © 2026 Vibe44. Built with ❤️ by Scale with Alap.