Email me
← Projects

Web

Client site platform

One component library, several client sites, and the release discipline that keeps them apart

Active Started 3 Apr 2026 Updated 24 Jul 2026
Built with
  • astro
  • typescript
  • css
  • npm
Component library workspace showing multiple client website layouts

Every small business site I build wants the same eight or nine things: a header that survives a long nav, a hero, a few content bands, a contact form that actually posts somewhere. Building those again per client is how you end up with nine slightly different headers and a bug fixed in one of them.

So the components live in one place — @iceniweb/ui, a published npm package — and each client site is a thin layer of configuration and content on top.

What’s in it

  • A shared component library. Headers, footers, and a set of content blocks — heroes, features, galleries, pricing, testimonials, CTA bands. All of them take their colours and spacing from CSS custom properties, so a client theme is a token file rather than a fork.
  • A monorepo. The package is the source of truth; client sites sit beside it and are linked in local development, so a component change is visible in a real site immediately.
  • Per-client integrations. Static sites are not the same as sites that can’t do anything. The most involved one has its own project entry — a Shopify storefront running on a site with no server.

The interesting parts

None of the hard problems turned out to be component design.

The first is versioning. Client sites are separate repositories that deploy on their own, which means “it works on my machine” and “it works on the build server” are answering different questions about which version of the package is installed. That one has bitten me properly once.

The second is where the abstraction should stop. Every client wants something the library doesn’t do, and each time the choice is a new prop, a slot, or an admission that this is a different component. Get that wrong repeatedly and you have either nine headers or one header with forty props.

Still open

Publishing 0.7.0, then moving the sites that are pinned to older ranges onto it — which means checking each one, because a shared component library is only a saving until an upgrade breaks three sites at once.

Devlog

  1. The version that wasn't there

    A shared package that worked locally and could not have worked in production.