Skip to content

One codebase, many sites: building a template instead of a site

4 min read
processclient-work

Regional practices in the same sector need nearly the same website. Here is what changes when you build for the second client before you have the first.

Ten architecture practices need close to the same website: who they are, what they do, selected projects, how to reach them. Ten accountants need close to the same website. Ten letting agents do too.

Building each one from scratch means paying for the same decisions again every time — where the project grid goes, how the contact form validates, what the mobile nav does. The client pays for that, every time, and none of it is the part that makes their site theirs.

So for the architecture practice concept build I built a template instead, and the interesting constraint was this one: no component may hold a practice name, service description, project title or contact detail as a string literal.

What that constraint forces

It sounds like a small rule. It is not. If no component can name the client, then every piece of client-specific information has to live somewhere a component reads from, which means you have to decide up front what kinds of thing a practice actually has.

That produced four places, and only four:

  • site.config.ts — identity, contact details, deployment behaviour
  • theme.ts — every colour, font stack, radius, shadow, rhythm and motion value
  • src/content/ — typed content modules, one per section
  • a separate config for my own brand, kept apart on purpose so the byline and the demo banner survive a re-skin untouched

The theme file is the one that surprised me. A script generates the CSS variables from it, and the OG image and favicon import it directly — so the brand palette is never written down twice. Before that, re-skinning meant hunting for the one hex code someone had hard-coded into a social share image eight months earlier.

Typed content is the actual win

src/content/ holds typed modules rather than a CMS or a pile of markdown. A missing field is a compile error.

That is the difference between “the new practice’s site launched with an empty Services section and nobody noticed for a week” and “the build failed and I fixed it in thirty seconds.” For a template that gets stood up repeatedly by one person, moving that class of mistake from runtime to build time is worth more than any amount of admin UI.

Two deployment modes, and a safe default

The same codebase builds in two modes, switched by environment variable:

  • Portfolio — indexed, published as a piece of work
  • Prospect demonoindex on every route, a robots.txt disallow, and an empty sitemap

It defaults to the non-indexable one. That default is deliberate, and it is the kind of decision that only looks important once. If the variable is the thing that makes a demo safe, then forgetting the variable must fail closed — otherwise, sooner or later, a speculative demo built for one prospect turns up in search results under a real practice’s name.

The honest caveat

Nobody commissioned this. It was built to prove a point about structure, and the practice, the projects and the town in it are all invented — the live version says so on every page.

So what it demonstrates is narrow and specific: how quickly a well-structured codebase becomes a different business’s site. A full re-skin runs about three to four hours. It says nothing about what that site would do for anyone’s enquiry rate, and I would not claim otherwise.

What it does mean in practice is that if you are in a sector where the shape of the site is well understood, a lot of what you would otherwise be paying for has already been paid for.