Zalando — Micro Frontends: Deep Dive into Rendering Engine (Part 2)¶
Summary¶
Part 2 of Zalando's micro-frontends series opens up the
Rendering Engine internals
that Part 1 deliberately deferred. It covers: the Renderer
programming model (a fluent-interface set of lifecycle
methods — withQueries / withProcessDependencies /
withRender), the Rendering Engine's request lifecycle
(recursive Entity→Renderer resolution, streaming SSR,
progressive hydration, code splitting), the
data-fetching substrate (
Perron with circuit breakers +
DataLoader for per-request batching), how the same Renderer
code renders Native Apps via a custom React reconciler
emitting JSON, and Mosaic backward compatibility via
fragments embedded inside Renderers. A concrete end-to-end
page-rendering walkthrough (outfit page → Skipper →
rendering-rules tree → nested GraphQL resolvers →
streamed HTML) is given.
(Source: sources/2021-09-08-zalando-micro-frontends-from-fragments-to-renderers-part-2.)
Key takeaways¶
- Renderers are declarative. A Renderer is built via the
tile()fluent-interface API and describes — but does not execute — its own behaviour. The Rendering Engine runs the lifecycle methods in order, feeds outputs of earlier steps into later ones, and orchestrates data fetching. Contributors write three pieces:withQueries(...),withProcessDependencies(...),withRender(...); everything else is platform. See concepts/declarative-lifecycle-api. - Entity→Renderer resolution is recursive and non-blocking.
The Rendering Engine receives an entity-type + entity-id
header pair from Skipper (e.g.
entity-type: "outfit",entity-id: "ern:outfit::4NXOAez0Qti"), picks the root Renderer via rendering rules, runs its lifecycle, and as soon as the root Renderer emits child entities recurs into them. The process does not block on the whole tree — HTML starts streaming to the client as soon as any Renderer is ready. See concepts/recursive-entity-tree-resolution. - Web + Native Apps from one Renderer. Renderers output React elements. For the Web, the standard React renderer emits HTML (SSR) and the same element tree is later hydrated client-side. For the Native Apps, Zalando implemented a custom React reconciler that consumes the same React element tree but emits app-compatible JSON instead of HTML. Web developers can contribute native-app features using the same APIs, tooling, and platform. See concepts/react-custom-reconciler and the pattern patterns/same-react-code-web-and-native-via-reconciler.
- Data fetching is GraphQL + Perron + DataLoader.
Renderers declare GraphQL queries in
withQueries. The Rendering Engine fetches those against the Fashion Store API using an implementation of Perron — Zalando's open-source HTTP client with built-in circuit breakers, error handling, and retries — and all queries are batched and cached within the same request via DataLoader, preventing duplicate backend calls. - Progressive hydration + Code splitting + Renderer state
are shipped features. Specific Renderers can be marked to
hydrate early (becoming interactive before their parent);
only the Renderers needed for a given personalised page are
loaded and parsed; each Renderer has access to a local
Renderer State(similar toReact.setState) that re-runs the lifecycle methods and re-renders children when set. - Universal rendering with full SSR. Each Renderer first emits markup on the server; the Rendering Engine streams the stitched HTML to the client, and client-side hydration then wires the React components up with the same data. For Native, the same pipeline serves JSON instead.
- Mosaic backwards compatibility is a first-class feature, not a shim. Mosaic fragments can be used directly inside Renderers — not just as a separate page-level fallback. Mosaic is now framed as "a powerful API our framework supports" that is still used "sometimes." This gives legacy code path observability and platform integration benefits it lacked under raw Mosaic.
- Automatic Persisted Queries (APQ) are on by default. All GraphQL queries to the Fashion Store API are persisted server-side with a unique identifier; the Rendering Engine client runtime sends only the identifier, reducing request size. See patterns/automatic-persisted-queries.
- Web Vitals and custom client-side metrics are auto-collected. Developers contribute Renderers; the platform reports Web Vitals, log aggregation, OpenTracing, and client-side error monitoring "with zero-integration time for the Renderer developers."
Systems extracted¶
- systems/zalando-rendering-engine — now with internals filled in: Node.js backend + browser runtime, recursive entity-tree resolution, streaming SSR to client, custom reconciler path for Native Apps, Perron + DataLoader substrate, persisted queries, Web Vitals collection.
- systems/zalando-interface-framework — Renderer programming model now specified (fluent-interface tile API).
- systems/zalando-mosaic — now explicitly supported as an in-Renderer API, not just a page-level fallback.
- systems/zalando-graphql-ubff — confirmed as the Fashion Store API behind the Rendering Engine; queries batched/cached via DataLoader; queries persisted server-side via APQ.
- systems/perron-zalando-data-client — Zalando's open-source HTTP client with built-in circuit breakers, error handling, and retries; serves as the Rendering Engine's data-fetching substrate to FSA.
- systems/graphql-dataloader — the GraphQL community's canonical per-request batching / caching library used here to deduplicate backend calls during a single request.
- systems/skipper-proxy — named as the HTTP router that
matches the incoming customer URL to a route and forwards
the request to the Rendering Engine with
entity-typeandentity-idparameters.
Concepts extracted¶
- concepts/entity-based-page-composition — extended: now confirmed recursive; each Renderer can "suggest" child entities and has no control over the child Renderer choice (determined by the rules).
- concepts/react-custom-reconciler — first wiki instance. Reusing React's component model + reconciliation algorithm with a custom host-tree target that is not HTML (here: native-app JSON).
- concepts/declarative-lifecycle-api — first wiki instance. The fluent-interface pattern applied to a rendering framework: contributors configure behaviour by chaining lifecycle builders that the framework runs in a defined order, rather than writing imperative code.
- concepts/recursive-entity-tree-resolution — first wiki
instance. The specific tree-walk algorithm the Rendering
Engine implements: for each entity node, match to Renderer
via rules, run its lifecycle, extract any child entities from
its
withProcessDependenciesoutput, recur — without blocking the stream. - concepts/streaming-ssr — Zalando confirms streaming SSR is used: "the Rendering Engine kicks off the rendering process and starts streaming the HTML content to the client." Previously a Part 1 known gap.
Patterns extracted¶
- patterns/entity-to-renderer-mapping — extended: the
actual rendering-rule shape is now disclosed
(
selector: {entity: ...},renderer: "name", nestedchildren: [...]sub-rules). No longer just "declarative rules." - patterns/same-react-code-web-and-native-via-reconciler — first wiki pattern instance. Same React element tree, two host-tree targets (HTML via standard React renderer, app JSON via custom reconciler). The architectural win is that web devs can contribute to native with the same programming model, same tooling, same infrastructure.
- patterns/progressive-hydration — per-Renderer opt-in to early hydration, making specific renderers interactive before their parent finishes hydrating. (Concept pre-exists in React community; this is the wiki's first production Seen-in.)
- patterns/automatic-persisted-queries — first wiki pattern instance. Persist GraphQL queries server-side with an identifier; clients send only the identifier, reducing request size. Zalando adopts it as a default for Rendering Engine → FSA traffic.
- patterns/fragment-embedded-in-renderer-for-migration — first wiki pattern instance. During a platform migration, let the successor platform embed predecessor artefacts as first-class child units, not just a page-level fallback. Zalando's Renderers can contain Mosaic fragments; this turned Mosaic into "a powerful API our framework supports" and gave legacy fragments the successor platform's observability and integration.
Operational numbers disclosed¶
- The Part 2 post contains no new quantitative metrics beyond the ~90% traffic figure re-established in Part 1. No QPS, latency, Renderer count, Entity-type count, or Web-Vitals numbers are cited.
- The code examples use realistic Zalando resource names:
entity IDs follow the
ern:outfit::4NXOAez0Qtishape (Zalando Resource Name), and the outfit page's rendering rule tree is four levels deep (outfit_view → outfit highlight → product card | collection carousel → outfit card).
Caveats¶
- The Part 2 post still does not disclose the rendering-rules language formal grammar — only an example of its JSON-ish shape is given.
- Cache strategy for rendered HTML or per-Renderer output is not described. Remains an open question across Part 1 and Part 2.
- Failure isolation across Renderers — if one throws, what happens to the page? — is implicit (streaming + independent Renderer execution suggests isolation) but not explicit.
- Rendering-engine concurrency/backpressure model in Node.js is not described.
- Native reconciler implementation details (what the JSON looks like, how app clients consume it, how versioning works) are not disclosed — just that the reconciler exists.
- Migration economics (rewrite cost per Fragment → Renderer) still not quantified.
- Part 3 teased: comparison of Mosaic vs IF and migration lessons. An Update 2023/07 link at the bottom also points to a follow-up on React 18 Concurrent features. Neither is ingested here.
Source¶
- Original: https://engineering.zalando.com/posts/2021/09/micro-frontends-part2.html
- Raw markdown:
raw/zalando/2021-09-08-micro-frontends-deep-dive-into-rendering-engine-part-2-c05a499d.md
Related¶
- systems/zalando-interface-framework · systems/zalando-rendering-engine · systems/zalando-mosaic · systems/zalando-graphql-ubff · systems/perron-zalando-data-client · systems/graphql-dataloader · systems/skipper-proxy · systems/react · systems/typescript · systems/nodejs · systems/graphql
- concepts/entity-based-page-composition · concepts/micro-frontends · concepts/react-custom-reconciler · concepts/declarative-lifecycle-api · concepts/recursive-entity-tree-resolution · concepts/streaming-ssr
- patterns/entity-to-renderer-mapping · patterns/same-react-code-web-and-native-via-reconciler · patterns/progressive-hydration · patterns/automatic-persisted-queries · patterns/fragment-embedded-in-renderer-for-migration
- companies/zalando