Aggregation, Critical CSS, and a Layout Shift
A Drupal 10 site scores poorly on performance. CSS and JS aggregation are enabled, but the theme loads a large CSS file in the head and a web font via @import inside that CSS. Images in cards have no width/height, causing layout shift. A hero image is loaded eagerly with loading="eager" on every card image too. The team adds defer to all scripts, which breaks a small inline script that depends on jQuery being loaded first. They want measurable improvements without breaking functionality, and they ask whether to use a contributed module or theme-level changes.
1. Prioritize the fixes and explain the reasoning for each.
Read the worked answer
Priority order by impact and risk:
- Font loading:
@importinside CSS blocks parallel download and delays render. Move to<link rel="preload">for the font and a<link>stylesheet, or self-host withfont-display: swap. High impact, low risk. - Layout shift: add intrinsic
width/height(or aspect-ratio) to card and hero images so the browser reserves space. Directly improves CLS. - Lazy loading: only the hero (LCP) should be eager; all below-the-fold card images should be
loading="lazy". Eager-loading every card wastes bandwidth and delays LCP. - Script ordering:
deferpreserves order for external scripts but inline scripts withoutdeferrun immediately and can run before jQuery. Move the inline script into a deferred file or wrap it to run after dependencies; do not blanket-defer. - Critical CSS: inline above-the-fold CSS and defer the rest to reduce render-blocking, but only after the above.
Reasoning: fix render-blocking and CLS first (largest, safest wins), then script correctness, then advanced techniques.
2. Module vs theme: how do you decide, and how do you verify the result?
Read the worked answer
Decision:
- Theme-level: image attributes, font loading, critical CSS, and template markup are presentation concerns and belong in the theme (or its libraries).
- Module-level: aggregation tweaks, advanced CSS/JS deferral, and preload orchestration are often better as a contributed module so they are maintained and testable, but verify it does not conflict with core aggregation.
Verification:
- Measure with Lighthouse/WebPageVitals before and after, warm cache, on mobile throttling.
- Confirm LCP element is the hero and that card images are lazy.
- Check the console for script errors after changing defer, and confirm the inline script still runs after jQuery.
- Re-test with aggregation on and off, since aggregation can change script order and expose ordering bugs.
Reasoning: separate concerns by responsibility, and always validate with real measurements and functional tests rather than assuming defer or aggregation is safe.