Critical CSS and Inline `<style>` Tags
Critical CSS and Inline <style> Tags
Warpkit's build inlines critical CSS into the prerendered landing page via
Beasties (scripts/inline-critical-css.ts, run as a postbuild step). Beasties
hoists every <style> tag on the page into <head> during that pass -
not just the main stylesheet <link>. A component-local
<style>{...}</style> JSX tag gets moved out of its original position in
the prerendered HTML.
The failure mode
If any component renders a <style> tag as a JSX child (e.g. for a one-off
@keyframes animation), the prerendered HTML Beasties produces no longer
matches the DOM structure the client bundle expects to hydrate - the
<style> node is gone from its original spot, relocated into <head>.
React sees a real structural mismatch and throws Minified React error #418
on every production page load. It won't reproduce in vite dev (no Beasties
pass) or an unminified dev build, which makes it easy to mistake for
something else entirely - the actual failing component's identity is
stripped out of the minified error message.
Diagnosing it
src/client.tsx overrides @tanstack/react-start's default client entry
(same convention as src/start.ts overriding the server entry - detected by
file path, no vite.config.ts change needed) to pass onRecoverableError
to hydrateRoot. Its componentStack survives minification (built from
component/function names, not the stripped message), so a real production
hydration mismatch shows exactly which component tree it came from instead
of just an error code. If you see #418 and the stack points at a style
node, this is almost certainly the cause - confirm by grepping a real
production build's dist/client/index.html for the @keyframes/selector
content that should be inside your component; if it's sitting in <head>
instead, Beasties moved it.
The fix
Move component-local CSS into a real stylesheet - src/styles.css, or a
CSS Module if the project uses one - instead of a JSX-rendered <style>
node. A real stylesheet has nothing for Beasties to relocate, because
nothing about its position in the DOM tree is meaningful the way a JSX
child's position is.
// WRONG - a component-local <style> tag is a real DOM node Beasties can move
export function AnimatedWidget() {
return (
<div>
<style>{`@keyframes spin-in { ... }`}</style>
<div className="spin-in-item" />
</div>
);
}
/* RIGHT - src/styles.css, not relocatable because it isn't part of the JSX tree */
@keyframes spin-in {
...;
}
export function AnimatedWidget() {
return <div className="spin-in-item" />;
}