Skip to content

perf: inline CSS to drop render-blocking request#4

Merged
CybotTM merged 1 commit into
mainfrom
perf/inline-css
May 10, 2026
Merged

perf: inline CSS to drop render-blocking request#4
CybotTM merged 1 commit into
mainfrom
perf/inline-css

Conversation

@CybotTM

@CybotTM CybotTM commented May 10, 2026

Copy link
Copy Markdown
Owner

Why

Lighthouse Insights on the deployed site flagged three things on top of the 100/100/100/100 categories:

  1. Render-blocking requestsassets/style.css (~110 ms LCP impact)
  2. Use efficient cache lifetimes — fonts + CSS at 10 min TTL (~92 KiB savings)
  3. Forced reflow 61 ms [unattributed] — not from our code; we ship zero JS

This PR fixes (1). (2) is unfixable on GitHub Pages (Pages hard-codes Cache-Control: max-age=600 for static assets and we can't override; would need Cloudflare in front). (3) is Lighthouse's own measurement layout, not us.

What

  • scripts/build.py gains load_inline_css() that reads assets/style.css and rewrites url("fonts/...")url("assets/fonts/...") so the inlined @font-face URLs resolve correctly relative to the HTML document. The rewritten URLs match the existing <link rel=preload as=font> hints, so the preload is reused for the font fetch.
  • render_html() and render_index() take an inline_css argument.
  • templates/base.html.j2 replaces <link rel="stylesheet" href="assets/style.css"> with <style>{{ inline_css | safe }}</style>. Font preloads stay.
  • The CSS file is still copied to public/assets/style.css for direct consumers (PDF build, anyone hot-linking).

Lighthouse before vs after (local desktop preset)

Page Before After
/index.html render-blocking ~110 ms render-blocking 0 ms
/cv-executive.de.html render-blocking ~110 ms render-blocking 0 ms
/cv-technical.de.html render-blocking ~110 ms render-blocking 0 ms

All four Lighthouse categories stay at 100. Network dependency tree collapses to just the HTML document (no chained CSS request).

Trade-off

CSS is now duplicated across the three HTML files (~13 KB each, ~39 KB total). Acceptable: a CV is loaded once per visitor, the LCP win matters more than caching the same CSS across pages. The CV PDF still picks up the file from public/assets/style.css via WeasyPrint, so PDFs are unaffected.

The external <link rel="stylesheet" href="assets/style.css"> was the
only render-blocking resource on every page (~110 ms per Lighthouse on
GitHub Pages). Inlining it into a <style> block in <head> drops the
critical request entirely and collapses the network dependency tree to
just the HTML document.

scripts/build.py:
- New load_inline_css() reads assets/style.css and rewrites
  url("fonts/...") to url("assets/fonts/...") so the inlined font
  references resolve correctly relative to the HTML document. The
  rewrite matches the existing <link rel=preload as=font> hrefs, so the
  preload is reused for the @font-face fetch.
- render_html() and render_index() now take an inline_css parameter
  and pass it to the template.

templates/base.html.j2: replace the external <link> with
<style>{{ inline_css | safe }}</style>. Font preloads stay; favicon
stays.

The full CSS is also still copied to public/assets/style.css for any
direct consumer (PDF builds, hot-linking).

Local Lighthouse desktop preset, post-change:
- index.html         100 / 100 / 100 / 100, render-blocking savings 0 ms
- cv-executive.de    100 / 100 / 100 / 100, render-blocking savings 0 ms
- cv-technical.de    100 / 100 / 100 / 100, render-blocking savings 0 ms

Cache-TTL insight stays unaddressed: GitHub Pages hard-codes
Cache-Control: max-age=600 and we have no header override available.

Forced-reflow [unattributed] insight is not from us — pages ship zero
JavaScript.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings May 10, 2026 15:21
@CybotTM
CybotTM merged commit 5f06e5a into main May 10, 2026
5 checks passed
@CybotTM
CybotTM deleted the perf/inline-css branch May 10, 2026 15:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves initial render performance for the generated static CV site by eliminating the render-blocking external CSS request and instead inlining the stylesheet into the HTML output, while still emitting the standalone CSS asset for non-HTML consumers (e.g., PDF generation).

Changes:

  • Inline assets/style.css into rendered HTML pages via a new inline_css template variable.
  • Add load_inline_css() to rewrite font URLs for correct resolution when CSS is embedded in <style>.
  • Thread inline_css through render_html() / render_index() and update the base template accordingly.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
templates/base.html.j2 Replaces the external stylesheet <link> with an inlined <style> block driven by inline_css.
scripts/build.py Loads and rewrites CSS for inlining, passes inline_css into all template renders, while continuing to copy assets/ to public/assets/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

2 participants