
The brief: take CricNepal — a high-traffic, live-scoring cricket site running as a single WordPress theme — and turn it into a modern, headless platform. The constraints: it’s indexed, it’s live during matches, and it can never go dark.
We treated it as an architecture problem — event-sourcing, a real API, the strangler pattern — not a rewrite.
Starting point: the monolith
One WordPress install, one theme (rhinox2) doing everything — rendering, business logic, data access, presentation. Fine for a blog; wrong for a live sports product where scores change ball-by-ball and entities carry deep statistical histories.
1. Event-sourced live scoring — CN-Engine
Scoring was the first thing to leave the theme. We built CN-Engine, which records every delivery as a structured ball_event and derives all state from that event stream — score, on-strike batter, over rotations, fall-of-wickets, scorecard, commentary. Editing a mid-innings ball re-walks the entire chain deterministically: runs re-credited, strike rotations re-synced, no drift. State is computed, never a mutable field.
2. A domain REST layer
We exposed a proper API (a WordPress mu-plugin “domain API”): match bundles, player/team/tournament profiles, standings, archives, rankings, search. This seam reframed WordPress as CMS + engine behind an API, not “the website.”
3. CN-Engine v2 — operator-grade scoring
Live scoring is a high-pressure operator tool. v2 reworked the IA (grouped lifecycle nav, unified cockpit), added single-tap extras, and added loud guardrails: impossible states (e.g. a dismissed batter still at the crease) are detected and the operator is told exactly how to repair them — never silently corrupted. We also hardened the engine: canonical team-id resolution across all scoring paths, historical scorecard backfills, and a fix where a repair tool reported success while the replay layer silently rejected the input.

4. Design system v2026 + a two-layer token architecture
A unified identity from the Nepal kit (navy #2B2F4B, red #EC3339, white). Under it, a two-layer token system: semantic tokens (--surface, --text-heading) mapped to a Tailwind --color-* layer. That separation enabled a genuine dark mode — every surface/border/text token flips. We ran a full audit to eliminate hardcoded light values (the insidious kind, like a card hover that only broke in dark mode) and fixed the token bridge so bg-surface-* utilities flip correctly.
5. Figma as source of truth

Tokens, chips, and components live in Figma as the canonical system — design and code share one vocabulary, so a format pill in Figma is the same in production down to the accent border.
6. Account layer on Auth0
A fan account layer (follow topics, saved stories, quizzes, reading history) on Auth0, with its cn_session cleanly separated from WordPress’s editor login — two independent session layers, never conflated.
7. SEO + Schema.org, code-owned
SEO was first-class and server-generated: titles, meta, canonicals, OG, and rich Schema.org graphs (SportsEvent / Person+Athlete / SportsTeam). Sitemaps, robots, and feeds proxy through the same domain so nothing drops from the index during the migration.
8. The decouple — making “headless” actually true
The unglamorous core of the project: we moved everything the frontend depends on out of the theme and into the domain API — auth/account, quiz, follow-topics, nav menus, entity cards, photo galleries, the tournament header, team/player structured data. The theme becomes optional; the API becomes the contract.
9. SSR headless frontend
The experience layer is now a server-rendered Next.js app (App Router, ISR), hydrated into rich islands. Server renders real data, not skeletons — correct first paint for users and crawlers. Players, teams, tournaments, match centre, archives, search, rankings, homepage — all structured React fed by the API.
10. Structured data for entities
Player/team pages — profiles, career stats, rankings, debut records, squad memberships, recent matches, galleries, tagged news — converted from theme-injected HTML into typed endpoints + structured components, so the same data renders identically on the site, in islands, and in any future client.
11. Zero-downtime cutover (strangler pattern)
We ran the headless app alongside WordPress and migrated one route family at a time at the edge (nginx): /players as a canary → verify → teams → tournaments → matches → homepage. WordPress serves everything not yet migrated. Rollback is a single config change.
The production release was 148 commits promoted cleanly from staging, deployed with the live scorer sidecar rebuilt and Auth0 login verified healthy before the first route flipped. A real-world gotcha we solved: the origin sits behind Cloudflare with a Cloudflare Origin CA certificate, which Node’s fetch couldn’t verify — so server-side rendering 404’d until we added the CF Origin CA root to the trust store (no TLS weakening). Fans never saw a maintenance page.
Stack
WordPress (CMS + editorial) · mu-plugin domain REST API · CN-Engine event store · Next.js 16 SSR/ISR · Auth0 · Tailwind CSS + two-layer design tokens · Figma design system · RunCloud + Node supervisor sidecars · Cloudflare + nginx edge routing.
Takeaway
We turned a live WordPress monolith into a headless platform without a single minute of downtime — by treating the migration as an architecture problem rather than a rewrite. The same API now powers the website and can power mobile apps, partner widgets, and data products. WordPress does what it’s great at; the experience layer evolves on its own.
