Headless CMS · Editorial design
Northline Studio
Marketing site for an architecture practice. Pages are built from blocks by someone who has never seen the code, previewed as drafts, and live within seconds of publishing.
- Role
- Design and build
- Stack
- Next.js 16 · Sanity · TypeScript · Tailwind 4 · Playwright

- 3 files
- to add a page-builder block
- 7 route types
- covered by automated axe checks
- 0 deploys
- required for an editor to publish
- 84 images
- credited and managed through Sanity
01
The problem
Architecture practices need an image-led site that feels authored, but they also need to publish projects and journal entries without waiting on a developer. A completely free-form page builder solves the second problem by creating a new one: inconsistent pages that slowly lose the visual system.
Northline is a practice built to test the harder middle ground—a constrained set of expressive blocks, a real draft-and-publish workflow, and a production site that stays statically rendered.
02
Design
The art direction borrows the confidence of an architecture monograph rather than a software landing page: oversized photography, quiet typography, compact project metadata, and strong changes in scale. The interface stays deliberately sparse so the work, location, year, and building type do the orienting.
Filters live in the URL, detail pages use repeatable editorial rhythms, and the journal shares the same system without looking like a recycled project grid. The design stays reusable without making every document feel identical.



03
Architecture
- 01Sanity Studio
- 02Typed block registry
- 03Static Next.js pages
- 04Tagged revalidation
Every page is a slug plus an ordered list of blocks. A central registry pairs each block schema with its React component, so adding a block means adding its schema, its component, and one registry entry. Unknown blocks fail soft in production while mismatched known blocks fail during development.
Published reads are tagged and cached. Draft mode switches to the drafts perspective, bypasses the cache, and enables visual editing. Publishing calls a signature-checked webhook that expires both the document tag and its type tag, updating the detail page and every listing that references it.
04
Interesting decisions
- 01
Constrain the page builder
Editors needed freedom to compose pages without being able to break the visual system.
- Choice
- A typed registry exposes a purposeful set of blocks instead of arbitrary markup or a universal section component.
- Tradeoff
- Each new layout idea needs a real implementation, but every published combination remains designed and testable.
- 02
Keep Sanity out of the public bundle
The registry is imported by both the Studio schema and public renderer, while Sanity's authoring runtime is client-only and large.
- Choice
- Type-only identity helpers preserve Sanity's inference without importing its runtime into the React Server Component graph.
- Tradeoff
- There is a small compatibility shim to maintain, in exchange for a clean public bundle and one shared registry.
- 03
Transform images at the source
Sanity assets already sit behind an image CDN, so sending them through a second optimizer adds cost and another cache.
- Choice
- A custom Next.js image loader requests the exact responsive transform directly from Sanity's CDN.
- Tradeoff
- The site depends on Sanity's image URL contract, but avoids duplicate processing and keeps responsive behavior explicit.
- 04
Invalidate the smallest useful surface
A published project changes its own page and any index that lists it.
- Choice
- The signed webhook invalidates document and type tags instead of redeploying the site or clearing everything.
- Tradeoff
- Tag relationships must be modelled carefully, but publishing remains fast and cached pages stay fast.
05
Accessibility
- WCAG 2.2 AA checks run with axe across all seven route types in the production-build Playwright suite.
- Alt text is required in the Sanity image schema, so an editor cannot publish an unlabelled content image.
- Skip links, visible focus, semantic filters, definition lists, native disclosure controls, and associated form errors work without pointer input.
- The motion layer collapses under prefers-reduced-motion, and content never depends on animation to become visible.
06
What I'd do differently
The strongest reusable piece is not a component—it is the contract between the authoring schema, block registry, cache tags, and renderer. That pattern can move into another CMS build with a completely different visual system intact.
For a client handoff I would add editorial guidance inside each block, a small visual-regression suite for the highest-variance compositions, and real publishing analytics. The demo proves the workflow; those additions would make it easier to govern over several years.
Have a similar project?