DEV Community

Cover image for Ship a Client Website in a Day: The Single-Config Pattern for Next.js
Cenk KURTOĞLU
Cenk KURTOĞLU

Posted on

Ship a Client Website in a Day: The Single-Config Pattern for Next.js

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;
Enter fullscreen mode Exit fullscreen mode

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 }}>
Enter fullscreen mode Exit fullscreen mode
// tailwind.config — tokens point at the variables
theme: { extend: { colors: { brand: "var(--brand)", accent: "var(--accent)" } } }
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)