One Engine, Many Brands
Separate brand from behavior
Lesson 1 of 3
What you'll learn
- Identify what is "brand" vs. what is "behavior"
- Move tenant identity out of hardcoded pages into config
- Resolve the active tenant at request time
A resellable product is one engine wearing different jackets. The learning logic, the routes, the components — that's the body, and it stays the same for every client. The name, logo, colors, voice — that's the jacket, and it changes per tenant. The whole game is keeping those two apart.
The mistake that kills resale is baking identity into components: a hardcoded title, a color literal, a logo import. Do that and every new client is a fork. Instead, store identity as data:
type TenantBrand = {
slug: string;
name: string;
logoText: string;
accent: string; // a token, resolved to a color later
voice: string; // tone for AI/copy
};
At request time you resolve which tenant is active — by subdomain, path, or the signed-in org — and pass its brand down. The components read brand.name, never "BuildBot".
The line
If changing it for a client means editing a component, it's in the wrong place. Brand belongs in config; behavior belongs in code. Cross that line only on purpose.
The challenge renders the same component for two tenants by swapping only the brand. Run it.
The header function never changes — only the brand data does. Run it for both tenants.
Next: how the look varies cleanly through design tokens.
Sign in to save your progress across devices.