built the entire editorial stack for a culture publication from scratch
.NET 10 · PostgreSQL · Next.js 16 · AWS S3 · DockerStreetbrat is a culture publication covering art, beauty, fashion, film, music, and more. Off-the-shelf platforms — Squarespace, WordPress — impose structure that fights a brand like this at every turn: rigid themes, lowest-common-denominator image handling, CMS UIs built for blog posts not editorial spreads. I built the whole stack from scratch instead: a .NET 10 API, a Next.js 16 reader site, and a bespoke admin that gives editors the tools they actually need — focal-point image picking, multiple hero layouts, a pitch inbox, newsletter broadcasts, and a full audit log. Four months from first commit to production.
Three things a generic platform can't give a culture publication:
Image control. Hero images are the product. Squarespace centers everything. I needed per-image focal-point picking so a portrait doesn't get its face cropped out on mobile, and per-article hero layout choice (stacked, split, title-over, full-bleed) so each piece can be art-directed.
An owned editorial pipeline. Pitch inbox → draft → review → publish is a workflow, not a blog. There's no plugin that maps cleanly to it without bringing three other things you don't want.
A reader experience that isn't a theme. The frontend needed to be the brand, not a customised template. That means total control over fonts (Druk), typography scale, spacing, and navigation — none of which survive a theme engine intact.
"We tried three platforms. They all made the site look like every other site."
— editor, kickoff
Three constraints, in order:
Editorial UX first, API second. I spent the first two weeks sketching the admin screens before writing a line of API code. The data model fell out of what editors needed to do, not the other way around.
Own the auth surface. A bespoke CMS with a weak login is worse than Squarespace. ASP.NET Identity with TOTP 2FA, account lockout, token-stamp revocation, and magic-byte validation on uploads — the security posture had to match the access model.
Server-first frontend. Next.js App Router with React Server Components by default. No client-side state that doesn't need to be client-side. Server Actions for all mutations. The reader site fetches from the .NET API at request time; there's no build-time export step to break.
The image pipeline is a single POST to the API: magic-byte check, dimension validation, strip EXIF, upload to S3, return a URL with the focal-point coordinate stored alongside. The Next.js <Image> component uses the coordinate to set CSS object-position — no client-side crop, no canvas, no lambda.
Every admin action — article create/update/delete, user changes, settings edits — writes to an append-only audit log. On-call for a one-person editorial team means you need to be able to answer "what changed and when" at 11pm.
Newsletter and pitch inbox were retrofitted. I built them late in the project, which meant retrofitting the domain model. Both should have been in the schema from week one — the access patterns were predictable.
One Next.js app for both admin and reader. The admin is a single Next.js app sharing the reader's routing. It works, but the admin and reader have meaningfully different caching and auth needs. I'd separate them into distinct Next.js apps behind the same reverse proxy next time — cleaner cache boundaries, simpler middleware.
Integration tests after launch. I delayed writing integration tests until after launch. The FluentValidation rules are correct, but I had to verify that by reading code rather than running tests. Three edge cases in the image pipeline would have been caught earlier.
"We finally have a site that looks like us."
— editor, launch day