Preprocess Order and the Vanishing Body Class
A developer adds a body class in the theme's .theme file using function mytheme_preprocess_body(&$variables) { $variables['attributes']['class'][] = 'has-promo'; }. The class appears on the homepage but not on node pages. A colleague's module also adds a body class via hook_preprocess_HOOK and that one shows everywhere. The theme also has a mytheme_preprocess_page that rebuilds $variables['page']['#attributes']. Template caching is enabled and the developer insists they cleared the cache several times. The site uses a base theme that itself implements hook_preprocess_HOOK for html and page. The team wants a reliable, cache-safe way to add the class on all pages.
1. Explain why the body class is inconsistent and identify the most likely ordering/caching cause.
Read the worked answer
Preprocess hooks run in a specific order: modules first, then the base theme, then the active theme, each sorted by weight/name. If the class is missing only on some routes, the culprit is usually not the hook name but what the hook targets.
Key reasoning:
bodyattributes are often populated frompageorhtmlpreprocess. Ifmytheme_preprocess_pageoverwrites#attributeson the page array, it can discard classes added earlier by the base theme or other preprocessors.- Cache context: body classes derived from route or content must declare cache contexts/tags, otherwise cached responses (e.g., anonymous page cache) can serve a variant without the class.
- The hook itself is valid; the inconsistency points to a downstream overwrite or a cached render array, not a typo.
Diagnosis: inspect the order of preprocess execution and confirm whether preprocess_page reassigns rather than merges attributes. Also verify that the class is not being added conditionally based on a variable only present on the front page.
2. Propose a robust fix that survives caching and theme hierarchy.
Read the worked answer
Merge rather than overwrite, and be explicit about cacheability.
- In
preprocess_page, use$variables['page']['#attributes']['class'][] = 'has-promo';or better, targethtml/bodyattributes through the appropriate preprocess and append, never reassign the whole#attributesarray. - If the class depends on content or route, add the corresponding cache context (e.g.,
route,url.path) and cache tags so the render cache varies correctly. - Prefer adding the class in
hook_preprocess_htmlforattributeson<body>when no content dependency exists, since that is the canonical place and runs reliably. - Clear caches and verify with the render cache enabled, testing anonymous and authenticated responses.
Reasoning: the class must be (a) appended after base theme processing, and (b) declared cacheable so the page cache does not serve a stale variant.