D11 Pipeline: HTML Renderer

Phase ④

HTML Renderer Pipeline

HtmlRenderer is the most commonly used main content renderer. It builds a full decorated HTML page from the controller's render array through two distinct methods: prepare() then renderResponse().

 
HtmlRenderer — Stage 1
HtmlRenderer::prepare() — Ensure #type page
Receives the main content render array. Goal: guarantee it becomes a
'#type' => 'page' render array (the <body> layer). Corresponds to page.html.twig.
 
Is main content already #type page?
 
 
✓ yes — already #type page
Nothing to do. Pass through directly to hook_page_attachments step.
✗ no — must construct page
Dispatch RenderEvents::SELECT_PAGE_DISPLAY_VARIANT
Allows subscribers to select which page display variant plugin wraps the main content in a #type page array.
SimplePageVariant
Default. No block decoration. Used when Block module is disabled.
BlockPageVariant
Default when Block module is enabled. Places blocks in configured regions around main content.
Custom variant
Contrib / custom modules (Layout Builder, Panels, Page Manager) can subscribe and override per-route.
 
hook_page_attachments() + hook_page_attachments_alter()
All modules get a chance to attach page-level assets (#attached libraries, meta tags, http_equiv headers) that aren't tied to a specific page component. Results are merged into the page render array's #attached.
Drupal 11

hook_page_attachments() is still the correct API. D11 adds typed hook declarations via #[Hook] PHP attributes, but the hook itself is unchanged. Layout Builder's LayoutBuilderRenderer (contrib-turned-core) integrates at the SELECT_PAGE_DISPLAY_VARIANT event level.

 
HtmlRenderer — Stage 2
HtmlRenderer::renderResponse() — Build <html> and emit Response
Works on the #type page array produced by prepare(). Wraps it in an
outer #type html render array (corresponding to html.html.twig).
1
hook_page_top() + hook_page_bottom()
Modules may prepend/append render arrays to the very top and bottom of the page (outside the theme regions). Common use: toolbar, contextual links wrappers.
2
Wrap in #type html
Creates the outer html render array containing: #type page, page_top, page_bottom, and page-level #attached assets.
3
Renderer::renderRoot() called on #type html
The Renderer service (formerly drupal_render()) processes the full render array tree. → continues to Phase ⑤
Note: render arrays remain PHP arrays — no actual HTML is produced until this step.
4
Return HtmlResponse
The HTML string from step 3 is wrapped in an HtmlResponse object (subclass of Symfony's Response). BigPipe may intercept at this point. → continues to Phase ⑥
 
Template layer summary
prepare() steps 1–2
Produces the page render tree → rendered via page.html.twig (and region.html.twig, block.html.twig, …)
renderResponse() steps 1–3
Wraps in the html render tree → rendered via html.html.twig (adds <html>, <head>, asset tags)
renderResponse() step 4
HtmlResponse object created. All CSS/JS libraries resolved via AssetResolver and written into <head> / <body> script tags.
 
→ Phase ⑤ Renderer & Theme (step 3)
→ Phase ⑥ BigPipe (step 4)

Next: Phase ⑤ Renderer & Theme System →