Skip to content

PATTERN Cited by 1 source

Fragment embedded in Renderer for migration

Problem

A platform migration between two frontend architectures — Mosaic (Fragment-based micro-frontends) to Interface Framework (Entity-and-Renderer-based) at Zalando — can take years. The successor platform wants:

  • a clean programming model for new contributions,
  • observability, monitoring, A/B-testing, tracking integration at the platform level,
  • the ability to rewrite legacy fragments incrementally without a flag-day cutover.

The naïve migration shapes:

  1. Big-bang rewrite. Halt feature work; port every Fragment to a Renderer; cut over. Not tractable at scale.
  2. Page-level fallback only. The new platform serves pure-new pages; legacy pages remain on the old platform. Prevents co-existence on a single page; can't migrate piecemeal.
  3. Strangler pattern with adapter shim. Each legacy Fragment gets wrapped in a thin adapter that mimics the new platform's contribution contract. Adds a layer to maintain; the fragment remains a second-class citizen.

A fourth shape exists if the successor platform can be extended to accept the predecessor's artefacts as native child units — not wrapped, not adapted, directly embedded.

Solution

Let the successor platform's composition primitive (Renderer) embed predecessor artefacts (Fragments) as first-class child units. One Renderer can contain zero or more Mosaic Fragments; the Fragment executes as it always has, but inside the Renderer tree.

The post makes this explicit (Source: sources/2021-09-08-zalando-micro-frontends-from-fragments-to-renderers-part-2):

"We knew that the migration from Mosaic to Interface Framework would not happen in a day. Our Mosaic codebase was extensive and actively maintained. Therefore, the Rendering Engine allowed Mosaic fragments to be used directly inside Renderers."

Zalando's re-framing of the resulting situation:

"In fact, we now view Mosaic fragments as a powerful API our framework supports, and we still use them sometimes. In addition, this opened up extra integration and observability benefits for the legacy implementations."

Three non-obvious consequences follow from this:

  1. Mosaic stops being legacy. Once Fragments are a native API of the Rendering Engine, their continued use is a design choice, not technical debt. Legacy framing ("we'd love to kill Mosaic") is replaced by capability framing ("this is one of several ways to contribute").
  2. Legacy gets the new platform's observability. A Fragment embedded in a Renderer inherits the Rendering Engine's Web Vitals collection, OpenTracing, error reporting, logging aggregation. The Fragment's own observability gap is closed without rewriting it.
  3. Migration becomes optional. A Fragment can stay as a Fragment indefinitely if there's no business reason to rewrite it. Rewrites happen when the Renderer-native contribution model adds enough value (typically: richer A/B testing, better data reuse, platform features the Fragment was re-implementing).

Mechanics

At the Rendering Engine level, embedding a Fragment inside a Renderer requires:

  • Fragment-as-child-unit type. Alongside Entity-typed children, a Renderer's lifecycle can declare a Fragment (by URL or Fragment ID) as a child.
  • Fragment invocation inside the recursive resolver. When the recursive tree walk encounters a Fragment child, it invokes the legacy Mosaic composition path for that subtree, captures its HTML, and splices the result back into the Renderer's output.
  • Platform-feature injection around the Fragment. The Rendering Engine wraps the Fragment's HTML in the same observability + tracking envelope it gives Renderers.

The Fragment does not need modification. The Rendering Engine bears the complexity of supporting both composition models in one tree walk.

When to reach for it

  • A large legacy codebase exists that can't be rewritten all at once.
  • The predecessor composition primitive is well-defined enough to be invoked from a different framework (Fragments, micro-frontends via Web Components, pre-existing server-side includes, etc.).
  • The successor platform's programming model is good enough that teams genuinely want to adopt it — so the migration has pull, not push.
  • Observability/monitoring uplift for legacy is itself valuable (legacy Fragments in Zalando's case gained platform-level observability just by being embedded).

When not to

  • The predecessor primitive can't be invoked in isolation — it requires a whole page template or a specific request shape the successor platform doesn't reproduce.
  • Co-existence would be temporary anyway and the rewrite is known to be months away — the operational cost of supporting two composition paths exceeds the savings from incremental migration.
  • The primitives are semantically too different. Fragment-based UI that owns its own backend doesn't compose cleanly if the new platform assumes data comes from a central GraphQL layer (see systems/zalando-graphql-ubff).
  • Contract stability of the predecessor is poor. If the Fragment format keeps changing, the successor platform is signing up for a moving target.

Contrast with other migration patterns

Pattern Grain Legacy lifecycle
Page-level fallback whole page legacy owns pages it owns; no intermixing
Wrapper/adapter shim per legacy unit legacy wrapped to mimic new; always second-class
Fragment-embedded-in-Renderer (this pattern) per legacy unit legacy is a native child type; first-class
patterns/expand-migrate-contract per schema/feature schema-migration shape; not composition-level

The key differentiator of this pattern is that the legacy primitive is re-framed as an API of the successor platform, not a thing to be wrapped or wrapped-and-later- removed. Migration pressure becomes pull ("rewriting this Fragment as a Renderer gets me X"), not push ("all Fragments must be rewritten by Q3").

Caveats

  • Two composition paths to maintain. The Rendering Engine carries Fragment-invocation code forever, or at least until the last Fragment is rewritten.
  • Performance gap between paths. Fragments may not benefit from Renderer-specific optimisations (code splitting, DataLoader batching) even when platform observability applies to them.
  • Team ownership can drift. A Fragment still owned by its original team but now observed by the Rendering Engine platform team creates ambiguity about who fixes what when something breaks.

Seen in

Last updated · 550 distilled / 1,221 read