Skip to content

feat(build): allow opting out of critical CSS inlining for specific stylesheets via an HTML attribute (Beasties) #33284

@glimps-fde

Description

@glimps-fde

Command

build

Description

Since upgrading to Angular 21, the new build pipeline leverages beasties to optimize asset loading by automatically processing all <link rel="stylesheet"> tags found in index.html. While this works for standard static assets, it completely breaks setups that rely on runtime stylesheet overrides (e.g., multi-tenant white-labeling via Docker volume mounts or runtime file replacement).

The Technical Failure Mechanism:

  1. At Build Time (ng build):
    The external customization stylesheet (e.g., custom-theme.css) either does not exist yet or is present only as an empty/dummy placeholder file.

    • If the file is missing: The Angular build pipeline throws an error because beasties attempts to perform static analysis on a non-existent local asset path.
    • If the file is an empty placeholder: beasties processes it, extracts no critical CSS, but mutates the original HTML tag to enforce its asynchronous loading strategy (e.g., altering attributes, changing media="print" with an onload handler, or injecting preprocessors).
  2. At Runtime (Docker Deployment):
    The administrator mounts or overwrites the actual, populated custom-theme.css into the container's dist folder.

    • Because the <link> tag was mutated/rewritten during the build phase by beasties, the browser handles the runtime-loaded sheet with modified priority or asynchronous wrappers. This introduces severe race conditions, Flash of Unstyled Content (FOUC), or layout shifts, as the runtime styles are no longer parsed synchronously as intended by the developer.

There is currently no granular way to tell @angular/build / beasties to completely ignore a specific <link> tag and leave it completely untouched in the final index.html.

Describe the solution you'd like

We need a granular way to tell beasties / @angular/build to ignore a specific stylesheet.

Ideally, adding a standard or dedicated attribute like ng-skip-inline, data-beasties-skip, or simply respecting a data-inline="false" attribute on the <link> tag should signal the builder to copy the tag exactly as-is without attempting to read, prune, mutate, or inline its contents:

<link rel="stylesheet" href="styles.css">

<link rel="stylesheet" href="custom-override.css" ng-skip-inline>

Describe alternatives you've considered

  1. Disabling inlineCritical globally in angular.json: This fixes the Docker override issue but kills Core Web Vitals (FCP/LCP) optimizations for the rest of the application's actual global styles, which we do want beasties to inline.

  2. Injecting the <link> tag dynamically via JavaScript:

    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = 'custom-override.css';
    document.head.appendChild(link);

    While this successfully hides the link from beasties' static analysis during build, it's a runtime workaround for a configuration that should be handled cleanly at the compiler/build tool level.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions