Case Study

Magento 2 Migration with Hyvä Performance Theme

Complete platform migration from Magento 1 to 2.4 for an outdoor retail business. Featuring 35+ custom modules, modern Hyvä theme with AlpineJS and Tailwind CSS, sophisticated CI/CD automation, and specialised pre-order management.

35+
Custom Modules
99
Theme Overrides
5
Parallel CI Jobs
~60s
Pipeline Time

Project Overview

This case study examines a complete Magento 1 to Magento 2 migration for an outdoor retail business, encompassing platform modernisation with a focus on performance, maintainability, and business process automation. The project delivered a production-ready e-commerce platform featuring over 35+custom modules, a modern high-performance theme implementation, sophisticated CI/CD automation, and specialised business logic for pre-order management.

Show a modern high-performance theme implementation
The Hyvä theme framework replaced Magento's default Luma theme entirely. Hyvä uses AlpineJS for reactivity and Tailwind CSS for styling, dramatically reducing JavaScript overhead compared to the traditional RequireJS-based approach. This required creating 99theme override directories for third-party module compatibility.

The migration achieved its dual objectives: successful platform transition whilst simultaneously improving performance through modern frontend architecture, automated quality gates, and optimised development workflows.

Show automated quality gates
The CI/CD pipeline features 5parallelised static analysis jobs running simultaneously: Frontend PHP Code Sniffer, Frontend Mess Detector, Backend Copy Paste Detector, Backend Mess Detector, and Backend Code Sniffer. With fail-fast: false, developers see all violations at once rather than fixing one issue and waiting 3 minutes to discover the next. Total pipeline time: ~60sfor static tests, approximately 3-4minend-to-end.

Key technical achievements include complete pre-order business automation with reporting and a performance-focused theme implementation using modern PHP 8.1+ patterns with strict type declarations throughout.

Show strict type declarations throughout
100%of custom PHP files use declare(strict_types=1). Modern PHP 8.1+ features including enums, readonly properties, and constructor promotion were used throughout. This provides compile-time type safety and catches type-related bugs before runtime.

Technical Context

The project targeted Magento 2.4 Community Edition running on PHP 8.1 with strict type declarations throughout. The backend used Magento's architectural patterns to ensure upgrade compatibility.

Show magento's architectural patterns
Repository pattern abstracts data persistence, dependency injection provides loose coupling between components, and the plugin architecture enables extension without core modifications. All customisations use plugins rather than class rewrites, ensuring upgrade compatibility. For example, pre-order data is added to cart items via a plugin on Magento\Checkout\CustomerData\AbstractItem, not by rewriting the class.

For the frontend, the team replaced Magento's default Luma theme with the Hyvä framework, a fundamental architectural decision. Hyvä uses Alpine.js for reactivity and Tailwind CSS for styling, dramatically reducing JavaScript complexity compared to the traditional RequireJS-based approach.

Show a fundamental architectural decision
Luma's RequireJS-based architecture creates significant JavaScript overhead. Hyvä uses AlpineJS for reactivity and Tailwind CSS for styling, dramatically reducing frontend complexity. However, this decision required rebuilding compatibility layers for all third-party integrations (payment gateways, shipping modules, and marketing tools) all needed Hyvä-specific implementations. The evidence: 99theme override directories.

Infrastructure Stack

  • Database: MySQL 8.0 for primary data storage
  • Search: Elasticsearch 7.17.8 for catalogue search
  • Caching: Redis 6.2 for sessions and cache, Varnish 7.1 for HTTP caching
  • Message Queue: RabbitMQ 3.11 for asynchronous operations
  • Development: Docker with Warden for consistent environments

The project comprised 35+custom modules covering business logic, integrations, and optimisations, including payment gateway integrations. Theme customisation required 99override directories across Magento core and third-party modules to achieve Hyvä compatibility.

Show payment gateway integrations
Multiple payment gateways were integrated: Klarna for buy-now-pay-later options, SagePay for card processing, Braintree for PayPal and cards, plus various finance options for high-ticket items. Each integration required Hyvä-specific JavaScript patterns to work with AlpineJS reactivity. The Klarna integration, for example, uses an external script pattern that initialises payment functionality without breaking Alpine's reactivity model.

Technical Challenges

Challenge 1: Third-Party Module Compatibility with Hyvä

The Hyvä theme uses Alpine.js instead of Knockout.js, breaking all third-party modules designed for Luma. Evidence: 99theme override directories required to achieve compatibility.

Solution: Created compatibility layers for each integration using Hyvä's external script pattern. For example, the Klarna integration required an external script pattern that initialises payment functionality without breaking Alpine's reactivity model.

Show hyvä's external script pattern
External scripts in Hyvä are loaded via the init-external-scripts event, allowing third-party JavaScript to initialise after Alpine loads. For Klarna, this meant creating a script element dynamically, setting its source to Klarna's CDN URL, adding data attributes for configuration, and appending to the document head. The once: true, passive: true event listener options ensure the script only loads once and doesn't block the main thread.

Challenge 2: Pre-Order Attribute Inheritance

The pre-order extension only assigns attributes to the default attribute set. Enterprise Magento implementations often have multiple custom attribute sets that don't inherit these attributes. Products created with non-default attribute sets lacked pre-order functionality entirely.

Show the default attribute set
Magento organises product attributes into attribute sets (similar to templates). Different product types might use different sets. Clothing might have size/colour attributes, whilst electronics have technical specifications. The pre-order vendor extension assumes everyone uses the default attribute set, which is rarely true in production systems. When merchants created products with custom attribute sets, those products had no pre-order fields at all.

Solution: Data patch that programmatically assigns attributes to all existing attribute sets. The implementation iterates through all attribute sets for the catalog_product entity type, locates the appropriate attribute group, and assigns the pre-order attributes with correct sort ordering.

Challenge 3: Configurable Product Pre-Order Status

Cart items for configurable products contain the child SKU but reference the parent product object, causing pre-order status lookup failures. Pre-order labels were missing from the cart for configurable product variants.

Show configurable products
Configurable products in Magento represent items with variants (like t-shirts with multiple sizes/colours). The parent product is the "configurable" (not orderable itself), and child products are the "simple" variants (the actual inventory). When a customer adds "Blue T-shirt, Size M" to their cart, Magento stores the child SKU (the specific variant) but often passes the parent product object to view models and plugins. This causes lookups to fail when you need child-specific data like inventory status or pre-order information.

Solution: ViewModel that resolves the actual child product when needed. The implementation checks if the product is configurable type, and if so, fetches the child product by SKU before retrieving pre-order data.

Show fetches the child product by SKU
Using the product repository with the child SKU: $this->productRepository->get($item->getSku()). This retrieves the actual simple product (the variant) rather than using the configurable parent. The pre-order extension stores pre-order attributes on simple products, so this fetch is necessary to get the correct data. Graceful error handling ensures the cart doesn't break if the product lookup fails.

Technical Approach

Hyvä Theme Migration & Customisation

Traditional Luma theme performance was inadequate for modern e-commerce expectations. The RequireJS-based architecture creates significant JavaScript overhead. The team opted for complete theme replacement with the Hyvä framework.

Show requireJS-based architecture creates significant JavaScript overhead
Luma loads dozens of JavaScript files via RequireJS, creating waterfall loading delays. Knockout.js adds further overhead with its template binding system. Modern browsers no longer need AMD loaders. Native ES modules and HTTP/2 multiplexing handle dependencies efficiently. Hyvä eliminates this entire stack, using AlpineJS (15KB) instead of Knockout.js + RequireJS (150KB+).

Key implementations included 7custom view models encapsulating business logic (397 lines), 19Tailwind component CSS files for modular styling, a responsive hero image system, and a checkout fallback theme.

Show a responsive hero image system
Hero images are pre-generated at three breakpoints: 640pxmobile, 1024pxtablet, and 1536pxdesktop. An afterSave() hook on the configuration model triggers immediate generation when administrators save new hero images. This eliminates first-load resize delays. The first visitor after a content change gets pre-optimised images. WebP conversion happens simultaneously with graceful fallback for older browsers.

Performance optimisations extended beyond images to the checkout process itself, requiring a robust fallback strategy for maximum compatibility.

Show a checkout fallback theme
Checkout is revenue-critical. A customer unable to complete purchase due to JavaScript incompatibility is unacceptable. The fallback theme uses proven Luma patterns whilst the main theme uses modern Hyvä. User agent detection switches to the Luma-based checkout theme for browsers that don't support AlpineJS. The fallback includes 5template overrides and 2layout modifications.

CI/CD Pipeline & Code Quality Automation

Manual code review was insufficient for maintaining quality across 35+modules. Risk of regression with each deployment necessitated automated quality gates.

The GitHub Actions pipeline features 5parallelised static analysis jobs and integration testing. The matrix strategy runs simultaneously with fail-fast: false ensuring complete visibility into all violations.

Show matrix strategy
GitHub Actions matrix strategy creates parallel jobs from a single configuration. Each test type (Frontend PHP Code Sniffer, Frontend Mess Detector, Backend Copy Paste Detector, Backend Mess Detector, Backend Code Sniffer) runs simultaneously in separate containers. The fail-fast: false setting ensures all jobs complete even if one fails, providing developers with complete visibility into all violations. Without this, developers would fix one issue, wait 3 minutes, discover another, and repeat.

PHPUnit configuration enforces strictness with multiple quality flags, preventing untested code from merging.

Show multiple quality flags
beStrictAboutCoversAnnotation ensures tests document what they cover. failOnRisky catches tests with risky conditions. failOnWarning prevents warnings from being ignored. forceCoversAnnotation prevents untested code from merging. Every test must explicitly declare what it covers. These settings create significantly higher quality standards than PHPUnit's defaults.

Pre-Order System Implementation

Complex pre-order business requirements were not supported by the standard extension. The need for automated reporting and visual differentiation of pre-order products led to a two-module system extending third-party pre-order functionality.

Show need for automated reporting
The business needed weekly CSV reports showing all pre-order inventory: SKU, name, manufacturer, quantity, and expected delivery label. Reports are emailed to configured recipients every week automatically. A CLI command (bin/magento preorder:report:generate) allows on-demand generation. File locking prevents concurrent report generation from corrupting output.

Module 1: Attribute Management & Checkout Integration - Plugin adds pre-order data to cart items, handling the complexity of configurable products by resolving child products when necessary. Graceful error handling ensures failures don't break the checkout experience.

Module 2: Automated Reporting - Data transfer objects using PHP 8.1 readonly properties with constructor promotion for immutability. Report generation includes file locking for concurrent access safety. Weekly automated CSV reports are emailed to configured recipients, with CLI command available for on-demand generation.

Show data transfer objects using PHP 8.1 readonly properties
Constructor promotion with readonly properties reduces boilerplate from approximately 40 lines to 10. Immutability prevents accidental mutation bugs. IDE support remains equivalent to traditional getters. For example, ReportLine uses public readonly string $sku instead of a private property with a getter method.

Modern PHP 8 patterns did more than reduce boilerplate. Constructor property promotion with readonly modifiers created immutable value objects that the type system could verify at compile time. This eliminated an entire class of runtime errors before code ever reached production.

Type-safe dependency injection meant the container could validate the entire object graph during compilation. Missing dependencies became build failures, not customer-facing errors. The same patterns that accelerated development also enabled aggressive automation.

With immutable data structures and compile-time validation in place, the team could trust automated quality gates to catch regressions. CI/CD was not just deployment automation. It was architectural validation.

Quality automation creates a feedback loop that changes how developers approach architecture. When every commit triggers five parallel static analysis tools, code that passes those gates becomes inherently more maintainable. Patterns that satisfy automated checks also happen to be patterns that resist bugs.

The CI pipeline did not just catch violations. It taught the team which patterns worked at scale. Dependency injection passed type checks cleanly. Plugin architecture satisfied architectural analysis. Code that the tools approved was code that other developers could understand six months later.

This validation gave the team confidence to build complex customisations without fear. Pre-order logic could extend checkout behaviour through plugins rather than core modifications. The same automated quality gates that accelerated deployment also enabled aggressive extension of Magento functionality.

Measurable Outcomes

Code Quality Metrics

  • 35+custom modules built following Magento architectural patterns
Show magento architectural patterns
Repository pattern for data persistence abstraction, dependency injection for loose coupling, plugin architecture for extension without core modifications. All modules use service contracts (interfaces) rather than concrete implementations. Configuration via XML (di.xml, module.xml, events.xml) rather than hardcoded logic.
  • 100%strict types - all PHP files use strict type declarations
  • 99theme override directories for third-party compatibility
  • 15test files covering critical business logic
  • 6static analysis tools in the CI pipeline

CI/CD Performance

  • Build job duration: ~60-90s total time
Show ~60-90s total time
Composer install with 180+enabled Magento modules. Vendor directory caching reduced this by approximately 50% (from ~120-180s without cache). Cache key includes composer.lock hash ensuring dependencies only re-install when changed.
  • Static tests (parallel): ~60scompletion time
  • Integration tests: ~120-180s with MySQL + Elasticsearch
Show ~120-180s with MySQL + Elasticsearch
GitHub Actions service containers provide real MySQL 8.0 and Elasticsearch 7.17.8 instances. Tests run against actual databases, not mocks. This catches real-world issues like query performance, index usage, and search relevance that mocks cannot detect. Setup overhead (~30s) includes database schema installation and sample data.
  • Total pipeline: ~3-4minend-to-end

Configuration Complexity

Show 89 lines
Includes test suites for unit and integration tests, strict coverage requirements, bootstrap configuration, and PSR-4 autoloading mappings. The forceCoversAnnotation setting alone prevents untested code from merging. Significantly higher quality than PHPUnit defaults.
  • PHP CS Fixer rules: 47lines (19 rules)
  • PHPMD ruleset: 49lines (14 rule groups)
  • PHPStan configuration: 50lines
  • GitHub Actions workflow: 151lines

These metrics demonstrate a thorough approach to quality automation, with significant configuration investment yielding consistent, maintainable standards enforcement.

Show configuration investment
This level of configuration takes time to build and maintain. However, it pays dividends in catching bugs before deployment. Without automated quality gates, human code review becomes the bottleneck, and subtle issues (type mismatches, unused imports, duplicated code) slip through. The upfront investment in tooling configuration creates a self-enforcing quality culture.

Skills & Capabilities Demonstrated

Technical Skills

  • Magento 2 Architecture: 35+custom modules following Magento patterns including repository pattern, dependency injection, and plugin architecture
  • PHP 8.1+ Development: Strict types, enums, readonly properties, constructor promotion throughout
  • Frontend Modernisation: Hyvä theme with Alpine.js and Tailwind CSS replacing traditional Luma patterns
  • CI/CD Engineering: GitHub Actions with matrix strategy and parallel execution
  • Test Automation: PHPUnit with integration tests and strict coverage requirements
  • Plugin Architecture: Clean extension points without core modifications ensuring upgrade compatibility
  • Performance Optimisation: WebP conversion, image preloading, Varnish caching
  • Docker/Containerisation: Multi-service development environment with Docker and Warden

Domain Knowledge

  • E-commerce: Payment gateway integrations (Klarna, SagePay, Braintree), checkout flows
  • Inventory Management: Pre-order systems, backorder handling, manufacturer-specific lead times
  • Retail Operations: Automated reporting, manufacturer-specific logic
  • Migration Strategy: Magento 1 to 2 platform transition methodology

Notable Decisions

Checkout Fallback Theme: Separate Luma-based fallback theme specifically for checkout on incompatible browsers. Checkout is revenue-critical. A customer unable to complete purchase due to JavaScript incompatibility is unacceptable.

Pre-Generate All Hero Image Variants: Generate all responsive variants immediately when admin saves configuration rather than on first request. First-load performance critical for hero images (often LCP element). Evidence: three breakpoint variants (640px, 1024px, 1536px) generated on save.

Non-Blocking Matrix Strategy: fail-fast: false runs all 5static analysis jobs regardless of individual failures. Development velocity improved when developers see all violations at once rather than fixing one, waiting 3 minutes, finding another.

Readonly Properties for Data Objects: PHP 8.1 readonly properties with constructor promotion for all data objects. Immutability prevents accidental mutation bugs. Constructor promotion reduces boilerplate from approximately 40 lines to 10.

Related Services

How we can help with your Magento and e-commerce projects

Ready to eliminate your technical debt?

Transform unmaintainable legacy code into a clean, modern codebase that your team can confidently build upon.