Skip to content

Critical CSS

Frontend Optimizer generates above-the-fold CSS from the public page URL. It does not use a separate PHP critical-CSS package. The default generator is PlaywrightCriticalCssGenerator, which runs resources/js/generate-critical-css.mjs with the package-local playwright dependency and opens the page in Chromium.

Install the package in a host Capell app with Composer:

Terminal window
composer require capell-app/frontend-optimizer

For local package development, point Composer at ../packages/capell/capell-packages-4/packages/frontend-optimizer as a path repository, then require the same package name.

  1. PrepareRenderProfileAction receives the resolved asset sets, render context, optimization scope, and a representative public URL.
  2. ResolveRenderProfileAction builds a profile signature from assets, context, scope, and critical CSS settings. The hash changes when relevant viewport, fold, or inline-size settings change.
  3. The profile is persisted and GenerateCriticalCssJob is queued when automatic generation is enabled and no generated CSS exists.
  4. GenerateCriticalCssAction calls CriticalCssGenerator; the default binding is PlaywrightCriticalCssGenerator.
  5. The generator writes a JSON payload containing the target URL, eligible stylesheet paths, configured viewports, fold settings, wait strategy, timeout, and max inline CSS size.
  6. The Playwright script opens the real URL in Chromium for each viewport and inspects document.styleSheets against the rendered DOM.
  7. CSS rules are kept when their selectors match visible elements intersecting the configured fold. Matching @media and @supports rules are preserved around their matching nested rules. @font-face and keyframes are included.
  8. The generated CSS is stored on the local disk under capell/frontend-optimizer/critical-css/{profile-hash}.css.
  9. Later renders inline that file as <style data-critical-css> before profile asset tags.

This URL-based flow is deliberate. It lets the generator see the final Blade output, Layout Builder content, theme markup, package render hooks, responsive CSS, and browser layout instead of guessing from source templates.

The configured viewports come from FrontendOptimizerSettings::viewports, falling back to capell-frontend-optimizer.playwright.viewports. The current defaults are:

[
['width' => 390, 'height' => 844],
['width' => 1440, 'height' => 900],
]

For each viewport, the script treats an element as above the fold when its visible rectangle intersects:

effectiveFoldHeight = viewportHeight * fold_multiplier + extra_fold_pixels

Use fold_multiplier to include more or less than one screenful. Use extra_fold_pixels for a fixed buffer below the viewport.

Only CSS assets are considered. A stylesheet is eligible when the asset signature marks it as critical_eligible, or when its slot/loading strategy indicates it can affect initial rendering:

  • base, head, or above_fold slots unless the loading strategy is lazy, idle, or interaction.
  • critical, blocking, or preload loading strategies.

If the profile has no eligible stylesheet paths, the browser-side collector falls back to inspecting all same-origin readable stylesheets.

Public rendering must not block on generation. Until CSS exists, RenderProfileAssetRenderer renders normal stylesheet output for the profile. If generation fails, GenerateCriticalCssAction records the failed run and leaves rendering on the fallback path.

Generated CSS is only inlined when all of these are true:

  • critical CSS is enabled in FrontendOptimizerSettings
  • the page type has not opted out
  • the stored CSS file exists
  • the file is within max_inline_css_bytes

If any check fails, the renderer keeps normal asset tags and omits data-critical-css.

Some page types are not worth optimizing. The package adds a page type setting:

meta.frontend_optimizer.disable_critical_css

When this value is true, pages using that page type do not queue generation and do not inline generated critical CSS. The normal profile asset output still renders.

The extension settings page is backed by FrontendOptimizerSettings and FrontendOptimizerSettingsSchema.

SettingPurpose
frontend_optimizer.enable_critical_cssEnables inline generated CSS.
frontend_optimizer.automatic_generationQueues generation when a profile is missing CSS.
frontend_optimizer.profile_scopeDefault optimization scope.
frontend_optimizer.viewportsViewport width/height pairs for extraction.
frontend_optimizer.fold_multiplierMultiplies viewport height for fold detection.
frontend_optimizer.extra_fold_pixelsAdds a fixed buffer below the fold.
frontend_optimizer.playwright_wait_strategyBrowser wait strategy: load, domcontentloaded, or networkidle.
frontend_optimizer.playwright_timeoutProcess timeout in seconds.
frontend_optimizer.max_inline_css_bytesMaximum generated CSS size allowed for inline output.
frontend_optimizer.debug_query_supportAllows non-production debug query support for isolated critical CSS checks.

The Node binary still comes from config and can be overridden with CAPELL_FRONTEND_OPTIMIZER_NODE.

Run the package tests from the repository root:

Terminal window
vendor/bin/pest packages/frontend-optimizer/tests --configuration=phpunit.xml