If you do client work, you know the time sink isn't building the site — it's rebuilding the same site for the fifth client with different colors, copy, and a different logo. The fix isn't "another framework." It's a discipline: drive the whole site from one config object so rebranding is data entry, not engineering.
Here's the pattern I use across 20 production templates.
1. One source of truth
Put everything brand-specific in a single typed file:
// src/lib/data.ts
export const site = {
name: "Lumen Agency",
domain: "lumen.studio",
brand: { primary: "#0ea5e9", accent: "#f59e0b", radius: "1rem" },
contact: { email: "hello@lumen.studio", phone: "+1 555 0100" },
nav: [
{ label: "Work", href: "/work" },
{ label: "Services", href: "/services" },
{ label: "About", href: "/about" },
{ label: "Contact", href: "/contact" },
],
services: [
{ title: "Brand Identity", desc: "...", icon: "Sparkles" },
{ title: "Web Design", desc: "...", icon: "Layout" },
],
} as const;
Because it's as const, TypeScript gives you autocomplete and catches typos in nav hrefs or missing fields before the build does.
2. Map design tokens to CSS variables
Don't scatter hex codes through 40 components. Feed the brand colors into CSS variables once, and let Tailwind read them:
// in the root layout
<body style={{ "--brand": site.brand.primary, "--accent": site.brand.accent }}>
// tailwind.config — tokens point at the variables
theme: { extend: { colors: { brand: "var(--brand)", accent: "var(--accent)" } } }
Now a rebrand is two hex values, and every button, badge, and link updates.
3. Components consume data, never define it
The anti-pattern is a Hero that hardcodes "Welcome to Acme." The pattern is a Hero that takes site.name and site.tagline. Same for nav, footer, pricing tables, and testimonials. The component owns layout; the config owns content.
export function Hero() {
return (
<section>
<h1>{site.tagline}</h1>
<p>{site.name} — {site.contact.email}</p>
</section>
);
}
4. Keep routing conventional
Next.js App Router already gives you multi-page structure for free — app/services/page.tsx, app/blog/[slug]/page.tsx. Use generateMetadata per route so each page gets its own title and Open Graph tags. Clients notice when sharing a link shows a real preview card.
Why this is the freelancer's unfair advantage
With this pattern, a new client engagement looks like: copy template → edit data.ts → swap logo → next build → deploy to Vercel. A day, not a week. You charge for the outcome, not the hours, and your margin goes up because the second, third, and tenth site cost you almost nothing.
I built 20 templates on exactly this pattern (SaaS, agency, restaurant, real estate, e-commerce, and more). You can see them live — digital agency, real estate, e-commerce — or grab the source on my site.
If you want a second pass before sending traffic to a client site, I also packaged a small audit for landing CTA, checkout/payment path, webhook/access unlock, and deployment blockers: https://productized-webdev.vercel.app/audit
Direct template checkout:
- 20-template bundle: https://cengokurtoglu.gumroad.com/l/vuhstz
- AI startup template: https://cengokurtoglu.gumroad.com/l/mmpgds
- Modern SaaS template: https://cengokurtoglu.gumroad.com/l/butotw Do you drive your client sites from config, or do you fork-and-edit each time? Curious what's working for other people doing volume client work.
Top comments (0)