Critical CSS deduplication
When server-side rendering, Archibald can inline the critical CSS of the matched bundles into a
<style> tag in the document <head>. Because the same rules can appear in multiple bundles, the
concatenated critical CSS often contains duplicates. The optimization.deduplicateCriticalCSS
option removes those duplicates so the inlined <style> stays small.
Configuration
Set the option in your archibald.json:
{
"config": {
"optimization": {
"deduplicateCriticalCSS": "modern"
}
}
}
The option is tri-state:
| Value | Behaviour |
|---|---|
false | Disabled. The critical CSS is inlined as-is (default when the option is omitted). |
"modern" | Recommended. Uses the PostCSS-based optimizer that preserves every modern CSS feature. |
true | Deprecated. Uses the legacy css-purge optimizer (crashes on modern CSS). Treated as "legacy". |
"legacy" | Deprecated. Uses the legacy css-purge optimizer (crashes on modern CSS). |
When the deduplication runs, the inlined tag is marked with id="optimized-critical-styles"
(otherwise id="critical-styles"). Deduplication only happens for production server-side renders.
What gets inlined
The critical <style> block inlines the CSS of the entry bundle, the modules rendered for the
response, and the above-the-fold islands — components whose Loadable declares
preloaded: true (header, navigation, …). Those islands render on the server, but their CSS lives
in async chunks that only apply once the island's JS chunk loads on the client. Inlining their CSS
into the critical block means it is applied on first paint, so the above-the-fold chrome does not
flash unstyled before hydration (FOUC). When critical inlining is enabled the otherwise-redundant
<link rel="preload" as="style"> hint for those islands is dropped; it is emitted only when
critical inlining is off, to warm the stylesheet without blocking.
Island stylesheets are resolved from the build manifest by chunk name. When the bundler's
SplitChunks logic divides a named async chunk into Name~0/Name~1/… variants, Archibald resolves
all of them, so a split above-the-fold island still has its CSS inlined.
"modern" vs the legacy optimizer
The legacy optimizer (css-purge) relies on a CSS parser that predates modern CSS. On such input
it raises a parse error and calls process.exit(1), which terminates the server process — so
enabling true/"legacy" on a project that uses any of the following will crash production
rendering rather than merely drop the rule:
- container queries (
@container,container-type) - cascade layers (
@layer) - native CSS nesting
- newer
@supportssyntax
Because the failure is a process.exit, it cannot be caught by the renderer's try/catch. The
only safe options are "modern" or false.
The "modern" optimizer is built on PostCSS and only ever rewrites whitespace and removes exact
duplicates, so all of the above are preserved untouched. It:
- merges sibling rules that share a selector, and sibling
@media/@supports/@container/@layerblocks that share a prelude; - removes exact-duplicate declarations (same property and value), keeping the last one so the cascade is preserved;
- keeps fallback declarations that share a property but differ in value (e.g.
display: -webkit-box; display: flex;); - never merges rules across at-rule boundaries, so rules in different
@container/@mediascopes stay independent.
Example
The following block is preserved as-is by "modern" but is dropped by the legacy optimizer:
.card-container {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 400px) {
.card {
font-size: 20px;
}
}
Deprecation
true and "legacy" are deprecated and log a warning at runtime. In v10, "modern" becomes
the default behaviour for true. Migrate by setting optimization.deduplicateCriticalCSS to
"modern". If you must keep the old behaviour after v10, set it explicitly to "legacy".