Case study · Modernising a global scientific content platform

A strangler-fig migration on a global scientific content platform

A platform where healthcare professionals access congress materials, publications, medical education assets, and clinical trial records. Two brands, multiple country sites, and a legacy frontend spanning server-side HTL, client-side Handlebars, Vue 2, and jQuery, sometimes all four in a single template file.

Role
Senior frontend engineer (via a consultancy)
Stage
2021–2025
Scale
37 components · two runtime dependencies
Stack
Lit 3, TypeScript, Storybook, HTL, AEM

Two stacks, one platform

Rather than a rewrite, the modernisation ran a Lit 3 plus strict TypeScript web component library (37 components across atoms, molecules, and organisms, on exactly two runtime dependencies) alongside the legacy stack, with both build systems running in parallel in production. The bridge between old and new is typed CustomEvent contracts: a central event name registry, one dispatch helper, and dedicated TypeScript payload files, making the event payload the cross-framework API. New Lit components embed inside legacy Vue pages purely through those events.

Rendering real AEM templates in Storybook

The piece of tooling I'd single out. Previewing an AEM component normally means deploying to an author instance and building a content page, a minutes-long loop. This pipeline compiles the production HTL templates inside Storybook using Adobe's own JS HTL engine: server-only directives are stripped before compilation, Sling model references are rewritten into a single properties context (the engine only accepts one), Storybook args merge over typed defaults, and the platform i18n API is stubbed in the preview head. The result is hot-reloading, controls-driven previews of the exact files the CMS serves. Nothing is forked, so preview and production cannot drift.

Component work worth mentioning

Shadow DOM breaks parent-side geometry queries, so truncating an author list to one line ("+N view more") became a measurement convergence loop: render optimistically, await the update, measure against single-line height, decrement, and re-render, a pattern reliable enough to reuse three times. Elsewhere: a dropdown with type-ahead, roving selection, and viewport-aware open-direction flipping; a search orchestrator with AbortController cancellation of stale autosuggest requests; and infinite scroll with IntersectionObserver pagination and skeleton cards. Component API docs are generated from source via custom-elements-manifest, so they regenerate on every build and cannot drift.

  • Lit 3
  • TypeScript
  • Storybook
  • Webpack
  • HTL
  • Handlebars
  • Vue 2
  • SCSS
  • AEM
  • Chromatic
  • axe

Back to selected work  ·  Previous: regulated forms platform  ·  See the engagement overview  ·  Talk to me about a similar project