Shipping a `CMS` driven blog with production hardening from the first commit.
Next.js 16 and Sanity blog for SeamHealth: editorial content, a newsletter with deliverability checks, rate limiting, CSP and CI.
- Lead engineer
- Jul 2026
- Small team; I wrote the initial implementation and most of the commits.
- Web · content site
- Next.js 16
- TypeScript
- Tailwind CSS
- Sanity
- GROQ
- React Hook Form
- Zod
- TanStack Query
- GitHub Actions
A SeamHealth Group site. The architecture is simplified.
Context
SeamHealth needed a blog for healthcare insights that editors could run without engineers: posts managed in a CMS, a newsletter, and good search and social previews.
My responsibility
I owned
- The initial implementation: Next.js app, Sanity schemas, GROQ queries and the blog feature
- The newsletter: subscription API with checks that an email address is real and deliverable
- Production readiness: rate limiting, a Content-Security-Policy and structured logging
- CI: a dedicated Sanity development project for builds, and retries for transient registry failures in the dependency audit
- Security upgrades, such as moving Next.js to a version that fixed four high-severity CVEs
I collaborated on
- Navigation, footer and design work with a second engineer
The challenge
A marketing blog is easy to build and easy to leave insecure: a public subscribe endpoint invites abuse, CMS content is untrusted input, and CI that depends on production content is fragile.
Constraints
- Editors, not engineers
- Content has to be managed entirely in the CMS.
- Public endpoints
- The newsletter API is reachable by anyone.
- CI isolation
- Builds shouldn’t depend on, or touch, production content.
Architecture
- Blog listing
- Post pages
- Embedded Sanity Studio
- OG image route
- blog
- newsletter
- GROQ queries → repositories → services
- Newsletter endpoint: validation, deliverability check, rate limit
- Sanity
- Pages never talk to the CMS directly: GROQ queries sit behind repositories, and business rules live in services.
- Skeleton loaders match each page’s structure, so loading doesn’t shift the layout.
Key decisions
01Verify email addresses before subscribing them
- A public subscribe form collects typos, disposable addresses and junk that harm sender reputation.
- Accept anything that passes a regex
- Check that the address is real and deliverable first
- Verification adds a step and a dependency; skipping it degrades the list and email deliverability over time.
- Validate with Zod, then check deliverability before adding the subscriber.
- A smaller, real list is worth more than a large one that bounces.
- Only deliverable addresses reach the newsletter list.
02Harden before launch, not after
- The newsletter endpoint and CMS-rendered pages were exposed to the internet from day one.
- Launch, then harden
- Add rate limiting, CSP and logging before launch
- Hardening first delays launch slightly; hardening later means running unprotected in public.
- Rate limiting on the API, a Content-Security-Policy, and structured logging before production.
- The cheapest time to add security controls is before anyone depends on the system’s current behaviour.
- Abuse protection and searchable logs were in place before production.
Implementation
- Content
- Sanity schemas, Portable Text rendering and an embedded Studio at /studio.
- SEO
- Dynamic sitemap, robots.txt and an edge-rendered social preview image per post.
- Tests
- Six test files, all written by me.
Production
- CI
- GitHub Actions against a dedicated Sanity development project, with a dependency audit that retries transient registry failures.
- Patching
- Next.js upgraded to fix four high-severity CVEs.
Outcome
Built from the first commit to a hardened, `CMS` driven site that editors run without engineers.
- Commits authored
- 19 of 32
- Including the initial implementation, from git history.
- Readership
- —
- Impact metric unavailable.
Lessons
Small public sites need the same basics as big ones rate limits, CSP, logs and patched dependencies. They’re cheapest when added at the start.
Next iteration
- Double opt-in confirmation for newsletter sign-ups.
- Preview deployments wired to Sanity drafts so editors can see posts before publishing.