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.

cricnepal brand

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.

ni5ch@ltiw@ri

Author ni5ch@ltiw@ri

More posts by ni5ch@ltiw@ri

Leave a Reply