Case Study
Enterprise Financial Web Platform
AEM Sites as a Cloud Service
Full-stack AEM development for a European financial services enterprise - building new components, integrating backend services, and improving platform quality through accessibility, performance, and test coverage.
Deloitte Digital · 2024-2025Overview
Led development of new features and components on an existing AEMaaCS platform for a European financial services client. Worked across the full AEM stack - Java backend (Sling Models, Sling Servlets, OSGi), HTL, Touch UI dialog customisation, clientlib architecture, and accessibility compliance. Delivered against strict quality standards with comprehensive unit test coverage.
Beyond technical delivery, I acted as tech lead for a team of 3–5 engineers - owning estimation, sprint organisation, and task breakdown in Agile. Worked directly with the client throughout the engagement to ensure business requirements were correctly translated into development features, managing SIT and UAT phases and coordinating the go-live plan. Maintained commercial awareness across the delivery, balancing scope, quality, and timeline at every stage.
Challenges
01
Adding components to an existing platform without regressions
The platform was already in production with real users. New component development had to integrate cleanly with existing architecture - Sling Models, JCR content structure, clientlib dependencies - without introducing regressions or breaking existing authored content.
02
Frontend performance: critical vs page-level clientlibs
The existing clientlib strategy loaded all component CSS and JS at page level. As the component library grew, this created unnecessary render-blocking resources on pages where those components were not present.
03
Touch UI dialog complexity
Several components required non-standard authoring experiences: conditional field visibility, custom validators, and multifield configurations. Standard AEM dialog patterns were insufficient and required custom Touch UI extensions.
04
Accessibility compliance across new and existing components
New components had to meet WCAG 2.1 AA requirements. Several existing components also had accessibility gaps that needed remediation without disrupting authored content or visual design.
05
Unit test coverage on Sling Models
The existing codebase had low unit test coverage on Java models. New models had to ship with comprehensive test coverage, and existing critical models needed retroactive test addition.
Solutions
Built new components end-to-end: OSGi-registered Sling Models for business logic and content mapping, Sling Servlets for custom endpoint exposure, HTL for server-side rendering, AEM Editable Templates with Style System policies for template and variant configuration, and Touch UI dialogs for authoring setup.
Sling Models & OSGi
Each component's business logic and JCR content mapping was encapsulated in an OSGi-registered Sling Model. Models were injected via @OSGiService and @ValueMapValue, keeping HTL scripts free of logic.
Sling Servlets
A custom Sling Servlet was implemented to handle form submission and integrate with an external mail service - covering a use case where the out-of-the-box AEM Forms and content APIs were insufficient. The Servlet was registered by resource type, keeping the endpoint co-located with the form component it served.
To manage the mail service credentials and endpoint configuration securely, a dedicated OSGi configuration was created. This allowed environment-specific values (API keys, sender addresses, service URLs) to be injected at runtime via AEM's OSGi config files - no hardcoded values in code, and no redeployment required to update configuration across environments.
This was a targeted, single-purpose integration - not a general pattern applied across components.
HTL
HTL was used as the server-side rendering language for all component markup, delegating business logic entirely to Sling Models via the Use-API. Beyond basic rendering, the implementation applied advanced HTL patterns to ensure robustness and maintainability:
- —data-sly-test for conditional rendering - preventing empty or broken HTML from reaching the DOM when optional model values were missing or null
- —data-sly-template and data-sly-call for intra-component templating - used when a component needed to cover multiple layout scenarios, keeping the markup clean and avoiding duplication
- —data-sly-use with scoped variables to compose complex component structures without logic leaking into markup
- —context attribute applied correctly where needed - for example, when working with the OOTB Embed component, context was set explicitly to avoid XSS vulnerabilities introduced by unescaped HTML output
The result was markup that was strictly presentational, resilient to missing content, and safe by default.
AEM Editable Templates & Style System
Component behaviour and visual variants were configured per-template via policy definitions - no code changes required to enable or restrict features per template. The Style System allowed authors to select pre-approved component variants directly from the Touch UI, with the corresponding CSS classes injected at runtime based on the active policy. This decoupled visual variation from component code, reducing the need for separate component variants.
JCR Integration
Component content persisted via Apache Jackrabbit (JCR). New node structures followed existing repository conventions to ensure clean merge with authored content already in production.
The existing clientlib strategy loaded all component CSS and JS at page level. As the component library grew, this created unnecessary render-blocking resources on pages where those components were not present. There was also no consistent naming convention, making it difficult to resolve the correct clientlib programmatically at runtime.
Critical clientlib (page <head>)
Contains only above-the-fold styles: CSS reset, typography scale, and layout grid. Kept intentionally minimal to avoid render-blocking. No component-specific styles.
Component-scoped clientlibs
Each component owns its own clientlib, loaded only when that component is present on the page - unused components contribute zero bytes to the page payload.
Naming convention & programmatic resolution
A naming convention was established across all component clientlibs - each clientlib category followed a structured pattern (e.g. project.component.<component-name>). At page component runtime, the AEM page component resolves and includes the correct clientlib programmatically based on the component name present on the page, without hardcoding a static list of dependencies. This made the inclusion mechanism self-maintaining as new components were added.
Webpack minification
All frontend JavaScript and CSS was minified via Webpack bundling for production builds, reducing asset payload and improving page load performance.
Impact
- —Eliminated render-blocking resources for components not present on a given page.
- —Naming convention made clientlib resolution automatic and consistent across the team.
- —Webpack minification reduced JS and CSS production payload.
Several components required non-standard authoring experiences that out-of-the-box AEM dialog patterns could not provide.
Granite condition-based field visibility
Fields shown or hidden dynamically based on authored selections - implemented via Granite UI conditions on dialog field nodes. Authors see only the fields relevant to their current configuration.
Custom validators
Field-level business rules enforced via clientlib-based validators attached to dialog fields. Validation runs before content is saved, preventing invalid data from reaching the JCR.
Nested multifield configurations
Repeatable content structures - lists of items with their own sub-fields - implemented with nested Granite multifields. All dialog extensions version-controlled and documented for team auditability.
New components had to meet WCAG 2.1 AA requirements. Several existing components also had accessibility gaps identified during audit that needed remediation without disrupting authored content or visual design.
Semantic HTML & landmark structure
Correct landmark roles (main, nav, aside) and heading hierarchy enforced across all new components. Existing components audited and corrected where heading levels were skipped or roles were missing.
Keyboard navigation & focus management
Interactive components (tabs, accordions, modals) fully navigable via keyboard. Focus trapped appropriately in overlays; focus restored to trigger on close.
Colour contrast
All text and UI elements verified to meet the 4.5:1 (normal text) and 3:1 (large text / UI components) contrast ratios required by WCAG 2.1 AA.
ARIA & alt text governance
ARIA attributes added for dynamic content regions. Alt text requirements for image components enforced at the Touch UI dialog level - authors cannot publish without providing alternative text.
The existing codebase had low unit test coverage on Java models. New models had to ship with comprehensive test coverage, and existing critical models were identified for retroactive test addition.
AEM Mocks (io.wcm)
Tests run against a simulated AEM context - JCR content loaded from JSON fixtures, OSGi service registry mocked, Sling resource resolution working without a running AEM instance. Fast, deterministic, CI-compatible.
Mockito for service dependencies
External OSGi service dependencies mocked with Mockito. Tests verified that models behave correctly under all injection scenarios, including missing or null service references.
Coverage scope
Test coverage included: content mapping from JCR nodes to model fields, OSGi service injection, edge cases for optional and missing content, and Sling Servlet request/response handling.
Retroactive coverage
Existing critical models identified as high-risk (high page coverage, complex mapping logic) received retroactive test suites. Coverage added without modifying production model code.
The financial services client operated under German regulatory requirements that mandated additional content disclosures on certain pages - legally required for the German market but must not appear on other regional sites.
HTL conditional rendering
Components were extended with additional optional authoring fields - for example, a disclaimer text field for the German section. At the HTL level, data-sly-test checked the page path or locale, ensuring regulatory content was injected into the markup only when rendering within the appropriate regional context.
Touch UI dialog visibility
Authors outside the German section would not see the regulatory field in the Touch UI dialog - controlled via custom JS logic attached to the dialog. This kept the authoring experience clean for non-German markets while enforcing compliance at the component level for those that required it.
No component duplication
No separate component variants or duplicated code were required. The same component served all markets - regulatory content was conditionally present or absent based on context.
The platform served multiple languages and countries, including right-to-left (RTL) languages. Component layouts built with directional CSS properties (left, right, margin-left, padding-right) would break or display incorrectly when rendered in RTL context - requiring either duplicated stylesheets or brittle overrides.
CSS logical properties
Directional properties were replaced with their logical equivalents across all affected components: margin-inline-start instead of margin-left, padding-inline-end instead of padding-right, inset-inline-start instead of left. Layout direction is driven entirely by the dir attribute (dir="rtl" / dir="ltr") set at the HTML element level on the page template.
Zero per-language overhead
No duplicate stylesheets, no per-language CSS overrides. The switch between LTR and RTL is seamless and automatic - adding a new RTL language requires no CSS changes, only correct dir attribute configuration on the page template.
Results
| Metric | Result |
|---|---|
| New components delivered | 10+ |
| Unit test coverage on new models | >90% |
| Clientlib payload reduction | ~35% on component-light pages |
| Accessibility issues resolved | 100+ across already existing components |
| Production regressions introduced | 0 |
Stack