Symptom
When navigating the site (changing pages), there's a brief flash of unstyled / raw-looking HTML before the page renders correctly. It happens on every page change. Observed on the deployed lecture-wasm site (built with myst build --html, theme v2.0.0).
Note: this is not the old hydration reload-loop (fixed in #63). The page renders fine once settled — the flash is a classic FOUC (flash of unstyled content) on navigation.
Root cause (two compounding factors)
1. Navigation is a full document load, not client-side (SPA) routing.
In the static build, internal links are plain anchors, e.g.:
<a href="/long-run-growth">…</a>
<a href="/eigen-i">…</a>
Clicking one destroys the JS execution context (confirmed via Playwright) — i.e. the browser loads a fresh document on every page change, so all CSS must re-apply each time rather than persisting across a SPA transition.
2. The critical render path includes render-blocking third-party CDN stylesheets.
Every page's <head> loads these remote stylesheets before it can paint a styled page:
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css
https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css
https://cdn.jsdelivr.net/npm/jupyter-matplotlib@0.11.3/css/mpl_widget.css
- font-awesome + jupyter-matplotlib are added by the theme in
app/root.tsx links() (lines ~44–51).
- katex CSS is injected by
@myst-theme (the math path).
On every full-document navigation the browser must fetch these third-party resources on the critical path. Chromium's paint-holding hides this (it shows blank → styled in my repros), but other browsers / real-world network conditions paint the document before the remote CSS resolves, producing the brief raw/unstyled flash. Self-origin assets (the bundled /build/_assets/app-*.css) load fast and consistently; the remote CDN round-trips are the variable, flash-inducing part.
Suggested fixes (in priority order)
- Self-host the third-party CSS in the bundle (serve from the theme's
public/, same-origin) instead of CDN <link>s — removes the cross-origin round-trips from the critical path of every page load. This is the high-leverage, low-risk fix:
- font-awesome and jupyter-matplotlib: change the hrefs in
app/root.tsx links() to local copies.
- katex: it's injected upstream; bundle the matching katex CSS locally and suppress/override the remote link.
- Fix the KaTeX version mismatch while doing the above: the linked CSS is
katex@0.15.2, but the theme pins/overrides katex to ^0.16.21 (package.json overrides). Bundle the matching 0.16.x CSS.
- (Optional, bigger) Inline critical CSS in
<head> so the first paint is always styled even before the full stylesheet loads.
- (Deeper) Investigate client-side navigation so page changes don't reload the whole document (CSS would then persist across navigations and the FOUC would disappear entirely). This may be inherent to the MyST static (
myst build --html) export — worth checking whether prefetch/SPA links are achievable.
Repro / evidence
- Static page
<head> contains the 3 remote stylesheets above (render-blocking).
- Internal links are plain
<a href> → full-page navigation (execution context destroyed on click).
- Under network throttling, the document paint-holds (blank) until the remote CSS loads; under conditions where paint-holding doesn't fully engage, that same gap surfaces as the unstyled flash.
Environment
- Theme:
quantecon-theme v2.0.0
- Consumer:
lecture-wasm (myst build --html → static deploy)
- mystmd: 1.9.1
Symptom
When navigating the site (changing pages), there's a brief flash of unstyled / raw-looking HTML before the page renders correctly. It happens on every page change. Observed on the deployed lecture-wasm site (built with
myst build --html, theme v2.0.0).Root cause (two compounding factors)
1. Navigation is a full document load, not client-side (SPA) routing.
In the static build, internal links are plain anchors, e.g.:
Clicking one destroys the JS execution context (confirmed via Playwright) — i.e. the browser loads a fresh document on every page change, so all CSS must re-apply each time rather than persisting across a SPA transition.
2. The critical render path includes render-blocking third-party CDN stylesheets.
Every page's
<head>loads these remote stylesheets before it can paint a styled page:app/root.tsxlinks()(lines ~44–51).@myst-theme(the math path).On every full-document navigation the browser must fetch these third-party resources on the critical path. Chromium's paint-holding hides this (it shows blank → styled in my repros), but other browsers / real-world network conditions paint the document before the remote CSS resolves, producing the brief raw/unstyled flash. Self-origin assets (the bundled
/build/_assets/app-*.css) load fast and consistently; the remote CDN round-trips are the variable, flash-inducing part.Suggested fixes (in priority order)
public/, same-origin) instead of CDN<link>s — removes the cross-origin round-trips from the critical path of every page load. This is the high-leverage, low-risk fix:app/root.tsxlinks()to local copies.katex@0.15.2, but the theme pins/overrideskatexto^0.16.21(package.jsonoverrides). Bundle the matching0.16.xCSS.<head>so the first paint is always styled even before the full stylesheet loads.myst build --html) export — worth checking whether prefetch/SPA links are achievable.Repro / evidence
<head>contains the 3 remote stylesheets above (render-blocking).<a href>→ full-page navigation (execution context destroyed on click).Environment
quantecon-themev2.0.0lecture-wasm(myst build --html→ static deploy)