Client projectLive
Box Populi
A site for a live techno improvisation collective out of New York City. The interesting part was never the landing page. It was the constraints. boxpopuli.live

Live and deployed on Vercel — the collective's public face for sets, roster, and bookings.
In simple terms
A site for a New York live-techno collective: a rotating cast of artists who play continuous, multi-hour improvised sets.
It carries the landing page, a full roster with a profile for every member, an in-page set player, and a booking form.
The interesting part was never the landing page, it was the constraints. The crew edits nearly every word and image themselves, from a portal on the site's own domain, with changes live in seconds and no developer in the loop. Underneath, several live streams are coordinated so that two can never play at once.
The impact. Real client work with real limits. A client who would not use GitHub, which sent the CMS plan back to the start. An iOS audio restriction surfaced honestly in the interface instead of papered over. Both are the places where sites like this quietly break.
What this is
Box Populi (“Box of the People”) is a rotating cast of NYC artists who play continuous, multi-hour improvised techno sets. The site is the collective's public face: a club-flavored landing page, a full roster with a profile page for every member, an in-page set player, and a booking form. It is content-managed end to end: the crew adds artists, announces shows, swaps the featured set, and rewrites any section without anyone touching code.
How it's built
Content lives in Sanity and is edited in a Studio embedded at /studio, themed to the site and forced dark. The site reads it through a thin adapter layer: async getters that map Sanity documents onto the same typed Artist and Show contracts the components always used, with the copy the site shipped with as a fallback for any field left blank. Components never import the CMS. That seam is why moving from typed files to a hosted CMS produced a rendered-text diff of zero across every page.
- Structure. A multi-page Next.js App Router app, not a single landing page: a home page (hero, manifesto, Listen player, crew grid), a roster index, a dynamically generated profile page for each artist, a Resend-backed booking form, and an unlisted, non-indexed sandbox for work-in-progress pieces and client conversations. Nearly forty prerendered pages in all, plus the Studio.
- Content model. Documents mirror how the client thinks, not a page builder: Site settings, Home page, Artists page and Contact page as pinned singletons that cannot be created twice or deleted, plus Artists, Shows and Roles as collections. Rich text is limited to paragraphs, bold, italic and links, so the section typography cannot be broken from the editor. Slugs are generated once and warned against changing. Shows move to past on their own once the date has gone.
- Publish to live. Publishing fires a signed webhook at an API route that invalidates one cache tag, and Next re-renders on the next request. Measured in production at about four seconds from publish to live page, with an hourly refresh as a safety net if a delivery is ever missed.
- Stack. Next.js 16 (App Router, React Server Components), TypeScript 5 end to end, React 19, Tailwind CSS v4 with Radix UI primitives and lucide-react. Geist plus a VCR display face for headings. Resend for the booking form, the official SoundCloud Widget API for audio, Vercel with CI/CD on push to main. Sanity for content, with the Studio embedded in the app and themed via
@sanity/themer, Portable Text for rich copy. Node 24, pinned to match Vercel. - SEO. The Next.js Metadata API drives titles, Open Graph, and Twitter cards, alongside robots and sitemap routes. The sandbox is excluded from indexing. Titles, descriptions and the share image are editable from the CMS.
Notable engineering
- One-at-a-time audio coordination. Several players can be on screen at once: a persistent header player that keeps playing across navigation, the Listen playlist, and per-artist related tracks. A small module-level registry (no React context) lets every player join a shared bus, and whoever starts playing pauses the rest. Because it is a module singleton, the coordination survives client-side navigation. The header keeps playing as you move between pages, but starting a track elsewhere still pauses it.
- Custom players over the SoundCloud Widget. The players are fully custom and on-brand (dark glass cards, a bespoke transport and tracklist) but driven by the official SoundCloud Widget API behind a hidden iframe. A single module centralizes embed-URL building and one-time script loading, so SoundCloud stays the sanctioned host while the interface stays on theme.
- The iOS first-tap reality, handled honestly. iOS Safari only starts audio inside a synchronous user gesture. A custom button calling
play()across a cross-origin iframe via postMessage is asynchronous, so the gesture is lost and iOS needs a second tap. Rather than hide this, the site shows a one-time “Double Tap” hint on touch devices that flips to the track name once playback starts. An overlay workaround was prototyped and rejected after on-device testing. An unofficial-API approach was rejected on terms-of-service and reliability grounds. Both calls are documented in the commit history. - No device sniffing. All responsive and capability behavior is CSS: width breakpoints for layout, input-capability queries (
pointer-coarse,hover) for touch-specific UI. NoisMobile, no user-agent checks. A narrow desktop window never sees the touch hint. - A CMS decision made by a constraint. The first plan was git-backed: content as files, GitHub login, a rebuild per save, and a branch ready to merge. Then the client said no to GitHub. That one constraint reordered everything. Git-based tools hidden behind hosted logins still meant a rebuild wait and photos committed into the repo, so the site moved to Sanity, the one CMS with a native Vercel integration, on a plan where the client's admin seat costs nothing. The old branch was deleted rather than parked.
- A migration with a zero-diff proof. Twenty-five artists, the shows, the roles, every page's copy, twenty images and the hero loop were pushed into the CMS by a one-shot script with deterministic ids, and a guard that refuses to run again without an explicit flag so it can never quietly overwrite the client's edits. The rendered text of every page was snapshotted before the switch and diffed after it. Zero differences, except one show whose date had passed and correctly left the upcoming list.
- Seconds to live, verified twice. The refresh path was tested locally with a hand-signed webhook request, then in production by publishing a change to one page's meta description and polling the live page until it changed. Four seconds. Reverting took another four. The same test surfaced that the canonical host is www and the apex redirects, which webhook senders do not follow, so the hook now targets www. Vercel preview builds only see variables ticked for Preview, and a Studio icons package one major version ahead passes type-checking and fails the bundle. Each is written down so nobody meets it twice.
Status
Live and deployed on Vercel. The Sanity CMS shipped as v1 in September 2026: roster, shows, page copy, images, playlists and SEO are all editable from the Studio, with an editing guide written for the client and a developer handoff for the code. The earlier Keystatic experiment was retired.
Skill set, end to end
Brand-faithful visual design, typed data modeling, Next.js App Router and React Server Components, Tailwind CSS v4, third-party API integration (SoundCloud Widget, Resend), cross-origin audio coordination, capability-query responsive design, SEO plumbing, headless content modelling for a non-technical editor, Studio theming and customisation, migration scripting, webhook-driven cache invalidation, and client enablement (editing guide, handoff, spec). Plus the judgment calls, documented in the commit history, about which workarounds to ship and which to reject.
The interesting part
It was never the landing page. It was the constraints. Multiple live players that cannot talk over each other. A sanctioned but clunky audio API kept behind an on-brand interface. An iOS limitation handled by telling the user the truth instead of hiding it. A client who would not use GitHub, which turned a parked CMS into a different one, and a better one. A content model the collective edits itself, so it grows without anyone touching a line of code.