Swedish state-owned energy company · Digital Sales · Solution Designer & Senior Developer
Enterprise CMS Migration: EPiServer 11 → Optimizely CMS 12
Led the migration of a large enterprise CMS platform powering digital sales — MyPages customer portal, contract signing flow, and public open pages. The migration moved from EPiServer CMS 11 on .NET Framework to Optimizely CMS 12 on .NET 8+, delivering a cloud-ready, maintainable architecture. A phased side-by-side approach kept end users unaffected throughout the transition.
Key decisions
- Feature Folder Structure — Codebase reorganised by business domain (MyPages, ContractSigning, OpenPages) rather than technical layer — each folder is fully self-contained with its controllers, views, models, and blocks.
- Side-by-side migration, phased by risk — CMS 11 remained live throughout. Open Pages migrated first (lowest business risk), then Contract Signing, then MyPages. One domain at a time — no big-bang cutover.
- Content Delivery API + Content Graph — Replaced legacy property traversal with Optimizely's GraphQL-based content querying — decoupled content delivery, more efficient page composition, no N+1 property fetches.
- Visual Builder for marketing autonomy — Enabled the marketing team to manage page layouts without developer involvement, reducing the volume of content-change tickets routed through engineering.
- .NET Framework → .NET 6 — StructureMap IoC replaced with built-in .NET dependency injection — clean container configuration with no third-party IoC overhead alongside the CMS upgrade.
- AngularJS → Angular Elements — MyPages interactive widgets migrated from AngularJS to Angular Elements (web components). Each Element is independently bootstrapped, lazy-loaded, and self-contained — Angular and the host page are fully decoupled.
Architecture decisionsLessRead more
Why Feature Folder Structure?
The existing codebase was organised by technical layer — all controllers in one directory, all models in another. Understanding any one feature required navigating multiple directories and holding the connections in your head. Ownership was unclear. Reorganising by business feature (MyPages, ContractSigning, OpenPages) made each area self-contained: a developer could understand MyPages by reading one folder. It also clarified team ownership — each product area had an unambiguous home. Onboarding time for new developers dropped measurably.
Phased migration, not big-bang
A full-platform cutover would have required months of parallel work with no user-facing value, and high co-ordination risk at the switch. The phased approach delivered working software at each stage. Open Pages went first — stateless, public-facing, and lowest business risk. Contract Signing followed; its well-defined input/output boundary made it a clean extraction. MyPages, which had the most complex authenticated state, came last. Custom migration scripts handled the content type mapping between CMS 11's property model and CMS 12's content model.
CMS 12 capabilities as force multipliers
Content Graph (GraphQL) replaced slow property traversal on content-heavy pages. Visitor Groups and Personalization enabled context-aware content for authenticated MyPages users without bespoke routing code. Optimizely Forms rebuilt customer-facing forms with validation and backend integration, replacing hand-rolled implementations. Scheduled Jobs automated content synchronisation. These features replaced bespoke code that the team had been maintaining — each one reduced the maintenance surface.
CI/CD pipeline as a delivery prerequisite
The existing deployment process was manual and error-prone. A phased migration over many months requires reliable, repeatable releases — manual deployment was not compatible with that cadence. An Azure DevOps pipeline with automated staging promotion and gated production release was built early in the project. It became the foundation that made incremental migration sustainable.
AngularJS → Angular Elements: migration with best practices
MyPages had interactive widgets (contract status, account details, usage graphs) built in AngularJS. Rather than a full rewrite or a prolonged AngularJS/Angular hybrid period, Angular Elements (web components via @angular/elements) was chosen: each widget compiles to a self-contained custom element, bootstrapped independently, and embedded into Razor views with a single HTML tag. Best practices applied: OnPush change detection to avoid unnecessary rendering cycles; explicit @Input and EventEmitter contracts so the host page has no Angular dependency; lazy loading per element so only widgets present on a given page are downloaded. AngularJS was fully removed from MyPages on the same release — no dual-framework overlap left in the codebase.