Take AG Grid's 187 KB of CSS off the critical path of every route

Performancekamo-internal
Shipped
August 26, 2026 at 2:11 AM UTC
Author
kamo
Commit
65eed6d

Batch 4 of the client-side performance audit, and the last of the big ones. app/globals.css opened with two @imports of ag-grid's stylesheets, and the root layout imports globals.css — so 187 KB of grid CSS sat in the <head> of all 143 routes and blocked first paint on every one of them, including /validate and /logout. Stylesheets are the only resource in this app that genuinely render-block, which made this the largest FCP item in the audit and precisely what PageSpeed reports under "Eliminate render-blocking resources". The layout's entry CSS drops from 334 KB to 147 KB. The imports move to **************** a wrapper that re-exports AgGridReact unchanged, and all 34 modules that imported ag-grid-react now go through it. A wrapper rather than 34 scattered stylesheet imports because the 35th grid is the one that would ship unthemed — a failure that looks like a CSS bug in a feature nobody touched. scripts/check-aggrid-import.mjs enforces it and runs in npm test. THE CASCADE, which is the part that needed proving rather than asserting. globals.css now loads BEFORE ag-grid instead of after, so every equal-specificity tie between ag-grid and Tailwind's preflight flips: preflight used to win them, ag-grid now does. Audited exhaustively with css-tree over both stylesheets rather than by eye: - [hidden] is (0,2,0) and still outranks all 167 ag-grid `display` rules at (0,1,0), so nothing hidden becomes visible. preflight's [role="button"] gives them — those ties are no-ops. The specialised coders (ew-resize, grab, move, cell) sit on resize and drag handles that carry neither [role="button"] nor :disabled. - All 20 of this file's own .ag-theme-alpine overrides are either fully !important or (0,3,0)+, so they are order-independent either way. - ag-grid's five bare element rules are <ag-grid-angular> and friends, element names this app never renders, plus a body custom property preflight does not set. One genuine flip: ag-grid ships `[class^=ag-] { outline: none }` and preflight ships `:-moz-focusring { outline: auto }`, both (0,1,0), so Firefox users would have lost the keyboard focus ring inside every grid. globals.css pins that one explicitly at (0,2,0) so it beats both regardless of order and the computed value is exactly what it was. Also in this batch: public/ is served with `Cache-Control: public, max-age=0`, so all 24 MB of icons, ringtones, logos and the pdf worker were revalidated on every hard load. Now one hour with stale-while-revalidate — deliberately not `immutable` and deliberately not a year, because nothing under public/ is content-hashed the way /_next/static is, so a file replaced by a deploy keeps its URL and `immutable` would pin the old bytes with no way to bust it. the launchpad. Those three call sites go through next/image, which serves a sized WebP of a few KB. The master file is left alone: CopilotHero renders the same asset at 148 CSS px and needs it. Verified: targeted tsc --noEmit clean over the wrapper, both next/image call sites and a sample of the rewritten grid modules with their transitive imports; all 9 guard scripts pass, including the new one, which was confirmed to fail on a planted direct import before being wired in.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing