0%
Vibe44 Logo

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

ItemFileChange
Name + taglinelib/config.tsconfig.app.name, config.app.description
Logopublic/Replace logo.svg, favicon.ico, og-image.png
Fontsapp/layout.tsxImport via next/font/google
Colorsapp/globals.cssOKLCH tokens (:root + .dark)
Email sender.env.localEMAIL_FROM, EMAIL_FROM_NAME
Social linkscomponents/global/footer.tsxFooter URLs
Legal/super-admin/legalAI Legal Generator
OG imagepublic/og-image.pngSocial 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);
}
  1. Edit OKLCH values
  2. Keep :root + .dark in sync
  3. 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-returns false when orgs disabled

Internationalization (i18n)

PropertyValue
Librarynext-intl
Localesen, es, fr
GateisI18nEnabled()
StrategyCookie-based (no URL prefix)
Sub-modules14 domains, 40+ namespaces
Globalscompany, 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 needed

Do NOT pass defaultTranslationValues as a prop to NextIntlClientProvider. That prop was removed in next-intl v4. Use i18n.ts instead.

Validation

ScriptCommandDetects
Missing keysnpx tsx scripts/find-missing-locale-keys.tsUsed-but-undefined keys
Missing translationsnpx tsx scripts/find-missing-translations.tsCross-locale gaps

Run both after modifying keys.


Analytics (Universal Adapter)

One env var:

NEXT_EXTERNAL_ANALYTICS_PROVIDER=datafast
ProviderEnv Var(s)Integration
DataFastNEXT_PUBLIC_DATAFAST_WEBSITE_ID + NEXT_PUBLIC_DATAFAST_DOMAINCDN
GA4NEXT_PUBLIC_GA_MEASUREMENT_IDCDN
MixpanelNEXT_PUBLIC_MIXPANEL_TOKENNPM
PostHogNEXT_PUBLIC_POSTHOG_KEY + HOSTNPM
PlausibleNEXT_PUBLIC_PLAUSIBLE_DOMAINCDN
PirschNEXT_PUBLIC_PIRSCH_CODECDN
UmamiNEXT_PUBLIC_UMAMI_WEBSITE_ID + HOSTCDN
Vercel— (auto)NPM
VemetricNEXT_PUBLIC_VEMETRIC_TOKENNPM

<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

FunctionUse 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:

TierPriceKey Limits
🔥 Free$0/mo3 projects, 50 AI credits, 100MB
⭐ Starter$29/moUnlimited projects, advanced AI, orgs
💎 Pro$49/moEverything + voice, priority support, unlimited AI

Customizing

OperationSteps
Add tierAdd key to PRICING_TIERS, set prices (cents), limits, provider env vars
Change pricesUpdate cents + create new entries in provider dashboard + update env vars
RenameChange name only. Never the key — stored in profiles.subscription_tier
RemoveisAvailable: false to grandfather subs
Change limitsEdit 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.