Blog and news lived in one mixed type, the sitemap was wrong, slugs were broken. I designed and hand-coded the replacement solo: a headless Sanity CMS driving a statically-exported Next.js site, piloted on the US site as the template every region now follows.
One team runs four sites here: Japan, Korea, China and English. The sites themselves shipped fast, built with web coding and AI assistance, and underneath them there was no CMS. Every piece of content lived in a single blog tab.
I had seen most of this before my first day, which is its own story.
Everyone researches a company before an interview. I ran TechSpy on them, the competitor tech-stack tool I had already built for exactly this kind of question. It read their public technical footprint: the stack behind the sites, DNS records, email sending setup, what was serving the content, and what the technical SEO looked like from the outside.
From that I could infer the shape of the problem before anyone described it to me: how content was probably being managed, where the workflow was likely to hurt, and which parts of the marketing site were quietly costing them. So in the interview I was talking about their actual situation instead of asking to hear about it. Six rounds were scheduled. The offer came after four.
The diagnosis in the next section is the same one I walked in with, confirmed from the insideA headless Sanity CMS feeding a statically-exported Next.js 16 site, content at build time only. Nine content types, a reusable block system, a self-owned lead-capture stack, and a hand-built Studio editing experience.
Before, blog posts and PR announcements shared one content type. A visit told you nothing, because the signal was mixed at the source. I rebuilt it as six separate programs, each with its own funnel stage, its own strategy, and its own conversion action: blog, newsroom and product updates work top-of-funnel toward newsletter signup; ebooks and webinars sit mid-funnel behind an email gate; customer stories carry bottom-funnel proof toward the demo.
The separation does two jobs at once. Navigation gets honest: each section is one kind of content with one purpose. And a product-led journey gets fed properly: a customer moving from first touch to using the product needs different content at every step, and the model now has a typed home for each of them.
newsroom absorbs external press via a newsType radio, so media mentions don't need a fake blog postEvery CMS form validates in two layers before a lead ever exists: a work-email check that prompts on public domains, and inline hints when an address is mistyped. Behind that, a Cloudflare Turnstile + Worker proxy does the server-side bot check on the demo form, layered with a honeypot and a blocklist.
The part that matters most is invisible: every form carries embedded hidden fields. The landing page, the full UTM set and the ad IDs (gclid, li_fat_id) are captured the moment a visitor arrives, persisted in sessionStorage across the whole browsing session, and replayed at submit, then passed through HubSpot's Forms API into the CRM, where the automations pick the lead up: routing, workflows, source attribution. Which page converted and which campaign paid for it are answered on every single lead.
Attribution survives the whole journey, from landing page to whichever page the visitor converts on
Popups are a CMS collection: marketers build several (newsletter capture, promo pushes) and flip which one is live, with no deploy. One schema covers both jobs (HubSpot email capture or a button) and both presentations (center modal or bottom-right slide-in), with a time-delay trigger and path targeting so lead-capture pages are excluded by default.
Frequency capping = a localStorage timestamp per popup, 7-day default suppress window, try/catch so private mode degrades quietly
A CMS-controlled bar pinned above the nav. Marketers toggle it, edit the message, and restyle it from 15 curated themes, 2 sizes, and 3 button styles. No deploy. A custom Studio input shows a live WYSIWYG preview before publishing.
One --banner-offset token, inlined on <html> during SSR, re-flows the whole layout with no first-paint jump
The old body field was text and not much else. Now every type renders through a typed block palette (inline CTAs, embedded video, tables, FAQs, pull quotes, stat highlights), and long-form pages carry a sticky right column with anchor navigation, so a reader can jump the article instead of scrolling it. Every block is a governed React component; there is no WYSIWYG blob anywhere in the system.
caseStudy carries a structured metrics[] array plus a customer quote. BOFU proof is a field type
A read-only, token-less Sanity client runs GROQ at build time inside server components; the projection does the shaping, so React gets flat, ready-to-render objects. Static export emits plain HTML: fast, secret-free, deployable to any static host including China. The sitemap and robots are CMS-fed and dynamic, fixing the old broken one. Deployment and the publish webhook sit with the China dev team. The rest of the system is mine end to end.
A toSanityLang() bridge maps next-intl locales to Studio languages; one Sanity project per region keeps each editor's Studio isolated
A CMS is a container. The reason one person can keep four sites and six editorial programs fed is that each type has its own multi-agent workflow standing behind it, and the workflow runs from research through to the published page.
For a blog post the chain runs competitor research, keyword research, brainstorm, outline, draft, then the visual and UX pass, with on-page and technical SEO handled as their own steps inside the same run rather than bolted on afterward. Each stage is a different agent with its own brief. Feature image and banner are generated in the run too.
That gets a piece to roughly 85% by my estimate. What is left is the part that has to be human: taking the AI flavor out of the language, and pushing the depth up to where it holds in front of a domain-expert audience. Two content types run further than that. News and product updates are close to fully automated, around 99%, because the source material is factual and the format is fixed.
Both percentages are my own working estimates. I have run no formal measurement, and the human step is a required stage in the workflowProduction and publishing are paired per content type: the workflow that makes a blog post has a matching workflow that files it into the CMS with its fields, its blocks, its SEO and its attribution already set. From there the same piece converts onward into a social post and a newsletter feature, so one production run feeds several channels instead of starting three separate jobs.
Attribution is attached at the content layer rather than added later, so every type can be traced through to the CRM. Across the six programs it adds up to one multi-stage, multi-layer system: research, production, publishing, distribution. That is what makes a one-person marketing function possible here, and it is the same pattern the content engines run at a larger scale.
Six programs, each with a paired production and publish workflow, all attributed back to the CRMA funnel dropdown would have been the easy call. Instead, each type carries the fields its role demands: TOFU types have no gate, MOFU types share a leadGate object, BOFU lives in caseStudy's metrics and quote.
webinar.status moves from upcoming to gated replay to public replay, with conditional fields and cross-field validation. The content's funnel role shifts as it ages, without splitting into three types.
Editors pick from 15 named presets, each pre-paired with a safe-contrast text color. Nobody touches hex, so the bar is on-brand and accessible by construction.
Most sites only get attribution when UTMs survive to the form page. I store them on landing and replay them at submit, with the hubspotutk cookie passed alongside.
Build-time GROQ plus static export: no server, no secrets in the bundle, no production CMS calls. The tradeoff (content ships on rebuild) is named and handled by the publish webhook.
Unique-slug validation on every type, soft-delete via an archived flag, protected singletons, and one Sanity project per region, so each region's editor owns exactly their own language in an isolated Studio.
The architecture is recent and not yet fully live on production, so these are the base and the trend it's feeding. They are real and screenshot-backed, but not a clean “I lifted X%” story.
5,749 contacts in HubSpot, ~2,002 added in the last ~90 days. That is the lead flow this capture + UTM system is already feeding, with the Turnstile/Worker gate guarding quality.
~20× Q2 organic impressions (1,253 in April to 24,847 in June). The position move from 4.6 to ~14.6 is the footprint widening: the site now surfaces for far more queries, many at lower ranks. Expected and healthy.
I started mid-June 2026 and many factors are in play, so the movement has more than one cause. Spam reduction and attribution coverage are still to be measured.
Sanity · GROQ
React
TypeScript
HubSpot Forms API
Tailwind
Content ops, automation, campaigns, retargeting. All built on this foundation.