Skip to content

perf: bundle animate.css instead of loading it from a CDN - #270

Merged
limonte merged 1 commit into
mainfrom
seo-bundle-animate-css
Aug 5, 2026
Merged

perf: bundle animate.css instead of loading it from a CDN#270
limonte merged 1 commit into
mainfrom
seo-bundle-animate-css

Conversation

@limonte

@limonte limonte commented Aug 5, 2026

Copy link
Copy Markdown
Member

Part of #259 — removes one of the two render-blocking third-party stylesheets.

Changes

  • animate.css added as a devDependency and @used from styles/styles.scss, the same way @docsearch/css and highlight.js styles are already imported there
  • the CDN <link> dropped from partials/head.html

4 files, +7 / −3.

Why import the package rather than extract the 9 keyframes

The issue suggested copying the keyframes the site actually uses into styles.scss. I measured first: the full published animate.min.css is 5.2 KB gzipped. Against a 15 KB CSS bundle that's cheap enough that hand-copying isn't worth its downsides — extracted CSS silently drifts from upstream and has to be re-checked by hand on every update. Importing the real package means the definitions are upstream's, and Renovate keeps them current.

Note the source/ files in the package can't be used directly: they carry unprefixed class names (.fadeInLeft), because the animate__ prefix is applied at the package's build step. Only the published file has the .animate__fadeInLeft selectors this site's markup depends on.

Cost

CSS bundle: 14,967 → 20,833 bytes gzipped (+5.9 KB).

Good trade for eliminating a blocking third-party request — a cold connection costs far more than 5.9 KB of same-origin, already-cached-with-everything-else CSS.

Verification

  • The CDN animate.css link is gone from all 23 built pages.
  • All 9 animations the site uses resolve to real rules in the bundled CSS — fadeIn/fadeOut × Left/Right/Up/Down plus headShake — each matched as an exact ^\.animate__X \{ rule, not a substring (animate__fadeInLeft would otherwise match animate__fadeInLeftBig).
  • .animate__animated, .animate__faster and the :root { --animate-duration } custom property they depend on are all present.
  • Spot-checked that .animate__headShake is wired to a defined @keyframes headShake.
  • bun run lint and bun run build pass.

Affected UI: the sidebar drawer (src/utils/sidebar.tsx) and the sidebars-drawers and draw-attention recipes. Worth opening the sidebar once after deploy to confirm it still slides.

Still open in #259

font-awesome 4 still loads from jsdelivr, so the origin remains on the critical path and its preconnect (#267) is still earned. Removing it means replacing 7 icons — fa-bars, fa-arrow-left, fa-arrow-right, fa-external-link, fa-info-circle, fa-thumbs-up, fa-thumbs-down — with inline SVG. That's the larger half, and it's what finally takes jsdelivr off the critical path. Lazy-mounting Sandpack also remains.

🤖 Generated with Claude Code

animate.css was pulled from jsdelivr as a render-blocking stylesheet.
Bundling it removes that request for 5 KB gzipped.

- add animate.css as a devDependency and @use it from styles.scss, the
  same way @docsearch/css and highlight.js styles are already imported
- drop the CDN <link> from the head partial

Imports the real published package rather than hand-copying the 9
keyframes the site uses, so there is no risk of the definitions drifting
from upstream, and Renovate can keep it current.

CSS bundle goes from 14967 to 20833 bytes gzipped. That is a good trade
for removing a blocking third-party request, and it is why the whole
file is imported rather than an extracted subset.

font-awesome still loads from jsdelivr, so the origin stays on the
critical path and its preconnect is still earned. Removing that one
needs its 7 icons replaced with inline SVG.

Part of #259

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@limonte
limonte merged commit 6f8bda4 into main Aug 5, 2026
1 check passed
@limonte
limonte deleted the seo-bundle-animate-css branch August 5, 2026 11:40
limonte added a commit that referenced this pull request Aug 5, 2026
Font Awesome 4 is EOL and was pulled from jsdelivr as a render-blocking
stylesheet, then downloaded a whole icon webfont, to draw 3 glyphs of
site chrome. With animate.css already bundled in #270, removing it takes
the last third-party stylesheet off the critical path.

- new src/components/UiIcons.tsx: IconBars, IconArrowLeft,
  IconExternalLink as inline SVG, sized in em so they scale with the
  inherited font-size and inherit text colour. All decorative, so
  aria-hidden - every call site has adjacent text or its own aria-label.
- .ui-icon in styles.scss nudges them onto the text baseline where the
  webfont glyphs sat.
- the example code in src/examples/functions.ts uses emoji instead:
  those snippets are displayed verbatim as documentation, so inlining
  SVG there would wreck them. Existing aria labels are unchanged.
- drop the font-awesome <link>, and with it the now-dead jsdelivr
  preconnect from #267.

No stylesheet is loaded from a third party any more. The remaining
external origins are all deferred, after-mount, or on-demand, so they
keep dns-prefetch only.

The stray fa-info-circle in validation-message-custom-icon-src.tsx is
left alone: it runs inside Sandpack, which only ever injects sweetalert2
and never had Font Awesome, so it was already not rendering. Pre-existing
and untouched by this change.

Part of #259

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant