The CSS-in-JS Hangover: Moving Past Runtime Styling

I spent three days last month debugging a flash of unstyled content on a client’s staging environment. Three days. The culprit? A legacy styled-components setup fighting a losing battle against React Server Components. It’s exhausting. We are still dealing with hydration mismatches from a styling pattern we probably should have retired a few years ago.

Look, I get why we all jumped on the CSS-in-JS train. Writing actual CSS right inside the React component felt like magic back in 2019. You didn’t have to worry about class name collisions. You just passed a prop and the button turned red. It was clean. It made sense mentally.

But the performance cost was massive. We were shipping megabytes of JavaScript just to parse strings into CSS at runtime.

The Runtime Trap

React programming code - Using React in Visual Studio Code
React programming code – Using React in Visual Studio Code

I remember running a profiling session on our main dashboard running Next.js 15.1 late last year. The JavaScript execution time was entirely dominated by style injection. And every time a user clicked a tab, the browser had to recalculate styles, generate new hashes, and inject them into the document head.

We eventually ripped out Emotion entirely. We dropped our memory usage on the Node containers by 42%. That’s not a rounding error — it’s a structural flaw in how we were building web apps.

The React ecosystem has moved aggressively toward Server Components. That architecture fundamentally breaks the context providers that runtime CSS-in-JS libraries rely on to collect and inject styles during SSR. You can hack around it, but you’re fighting the framework.

The Zero-Runtime Mirage

Then came the zero-runtime wave. Tools like vanilla-extract and Panda CSS promised salvation. Keep the component-scoped syntax, lose the client-side overhead. I migrated a medium-sized project to Panda last spring. It worked.

React programming code - Using React in Visual Studio Code
React programming code – Using React in Visual Studio Code

Mostly.

Here’s the gotcha nobody warns you about with zero-runtime extraction: your build times take a beating. On my M3 Max MacBook, our Vite build went from 4.2 seconds to over 18 seconds after adding the extraction step. The bundler has to traverse your AST, evaluate your styling code in a mini-runtime, extract the static strings, and generate CSS modules. When you have 800 components, that parsing isn’t free. You basically trade client performance for developer experience misery.

I found myself waiting on HMR updates that used to be instant. It broke my flow completely.

CSS code on screen - Add a Shared CSS Code Snippet
CSS code on screen – Add a Shared CSS Code Snippet

Where We Actually Are Now

The conversation around styling has shifted entirely because the problem we were trying to solve—component encapsulation—got solved better by utility classes. Designers don’t care about our styled-components props. They care about Figma auto-layouts behaving predictably in the browser. We’re finally seeing a wave of visual tooling that outputs actual, maintainable React code with standard Tailwind utility classes. No weird abstraction layers. Just standard flexbox and grid layouts that a browser can render without a 50kb JavaScript parser.

I tested one of these newer code-generation pipelines last Tuesday. I dropped a complex nested grid layout from a design file, and it spit out idiomatic Next.js code. Clean divs. Standard Tailwind classes. I didn’t have to map a single styled prop. It just worked.

If you’re starting a new React project today, avoid runtime CSS-in-JS. Just don’t do it. Use Tailwind. Use CSS modules if you hate utility classes. But stop shipping style parsers to users on 3G connections. The era of CSS-in-JS was a necessary stepping stone, but I’m glad we’re finally moving past it.

FAQ

Why is runtime CSS-in-JS like styled-components a problem with React Server Components?

Runtime CSS-in-JS libraries rely on context providers to collect and inject styles during server-side rendering, but React Server Components fundamentally break that pattern. This causes hydration mismatches and flash-of-unstyled-content bugs. You can hack around it, but you end up fighting the framework. The author spent three days debugging a styled-components setup clashing with RSC on a client’s staging environment before concluding the architecture is incompatible.

How much memory can you save by removing Emotion from a Next.js app?

After ripping Emotion entirely out of a Next.js 15.1 dashboard, the author saw Node container memory usage drop by 42 percent. Profiling had shown JavaScript execution time was dominated by style injection, with the browser recalculating styles, generating new hashes, and injecting them into the document head on every tab click. The author calls a 42% drop a structural flaw, not a rounding error.

Does vanilla-extract or Panda CSS slow down Vite build times?

Yes, significantly. After migrating a medium-sized project to Panda CSS, the author’s Vite build on an M3 Max MacBook went from 4.2 seconds to over 18 seconds. Zero-runtime extraction forces the bundler to traverse the AST, evaluate styling code in a mini-runtime, extract static strings, and generate CSS modules. With 800 components that parsing isn’t free, and HMR updates that used to be instant broke the author’s flow.

What should you use instead of CSS-in-JS for a new React project in 2026?

The author recommends Tailwind utility classes, or CSS modules if you dislike utilities, and says to avoid runtime CSS-in-JS entirely for new React projects. Utility classes solved component encapsulation better than styled-components ever did, and newer visual tooling now outputs idiomatic Next.js code with standard Tailwind classes, clean divs, and standard flexbox and grid—no styled props to map and no 50kb JavaScript style parser shipped to users.

Your email address will not be published. Required fields are marked *

Zeen Social Icons