- Published on
Why Server Components don't support CSS-in-JS?
Understand how a CSS-in-JS library works and why most are incompatible with Server Components.
- Time to read
- 6min read
Libraries like Styled-Components and Emotion don't work well with server components, and that's because of the need for runtime JavaScript to generate styles.
Server Components and Server-side Rendering
Before we dive into the topic, it's important to clarify the difference between Server Components and Server-side Rendering (SSR).
SSR as everyone knows is rendering an HTML page on the server side. Basically it's the concept of generating the entire HTML file of a page, which contains the initial UI, on the server side, and then sending it to the client. It basically follows these steps:
- User visits the page.
- The application's Node.js receives the request, which produces and returns the entire HTML of the page containing the initial UI of the page.
- When this HTML reaches the user's browser, React re-renders the same components, repeating the work done on the server. This process is known as hydration, instead of generating new HTML elements, it adopts the HTML elements already generated. In addition, it is necessary to add interactivity to the static HTML returned by the server, adding events, etc.
The main benefits of SSR are:
- Better SEO: Search engines prefer pages that are already pre-rendered and with complete content, as they can read the content of the page and index it better, without having to wait too long. With SSR, the HTML generated on the server already includes the rendered content, which facilitates indexing by crawlers.
- Better FCP (First Contentful Paint): FCP is a Core Web Vitals metric and an important metric for user experience. With SSR, the content of the page is loaded more quickly, even before the javascript loads, allowing users to see the content more quickly.
- Viewing the page without JavaScript enabled: Since the content is already rendered on the server and included in the HTML, users can still view the page even if JavaScript is disabled or blocked.
But this model has a limitation. All code written will be executed both on the server and on the client. There is no way to create components that will render only on the server side.
Let's consider the following code:
export default function Home() {
const posts = db.query('/posts');
return (
<main>
{posts.map(item => (
<Post key={item.id} {...item} />
))}
</main>
);
}
The code above would work just fine running only on the server, but when running again on the client it would produce an error, since the client does not have access to the database. In other words, it is not possible to tell React "Run this code only on the server".
Some of the most famous frameworks like Next.js came with their own solutions, where they add layers of code that run outside of React, and then run only on the server. In Next.js we have getServerSideProps
, which fetches the necessary data and then returns to the client in React props format. The problem is that such functions need to be at the beginning of the flow, at the top of the "page", it is not possible to add a getServerSideProps anywhere.
The React Server Components came to solve this problem. With them it is possible to make "server" features in React components. From version 18 of React, all components are server components by default, and to say that a component should run on both the server and the client, it is necessary to add the "use client"
flag.
So why CSS-in-JS libraries don't work in server components?
Well, there are several reasons why the mentioned libraries don't work well with server components. But the main reason is that they were created to run in the browser.
How does a CSS-in-JS library work?
Let's look at this code snippet that uses styled-components:
const Button = styled.button`
background: blue
font-size: 2rem;
color: white;
`;
export default function Dashboard() {
return (
<Button>Click me!</Button>
);
}
How does the Button component produce a styled button with the CSS rules we passed? The process is basically this:
styled.button
is a function that generates a dynamic React component with the HTML element sent as a parameter- A
<style>
element is generated with the CSS content and included in the<head>
of our HTML document - The unique CSS class generated in the
<style>
element is assigned to the<button>
element
This workflow works in both SSR and CSR (Client-side Rendering). However, as the user interacts with the application, it is necessary to create new styles, remove, or modify existing styles.
Let's think about the following case:
const Loader = styled.div`
color: grey;
font-style: italic;
`
function Form() {
const { formState: { isSubmitting } = useForm();
return (
<Form>
{isSubmitting ? (
<Loader>
) : (
...Rest of code
)}
</Form>
)
}
In the code snippet above, the styles for the styled Loader
component are not included in the initial CSS of the page, and then, when the form is being submitted, these styles should be included in the <style>
tag of our HTML. And that's the big point.
Internally, Styled Components makes heavy use of the React Context API. It uses it to manage the styles of the application, in a global value that can be shared between any component of the application. This way, any component can update the rendered styles of the page, as they are included/removed from the DOM tree.
Furthermore, we also know that Styled Components provide support for "Theming" through a <ThemeProvider>
that wraps the application, providing a global state that can be accessed by any component in the application, allowing the styles of the components to be dynamically adjusted based on the theme provided by the context. This is another feature widely used by the library that makes use of the Context API.
Unfortunately, as everyone knows, RSC do not support the Context API, and this is where the incompatibility lies.
Most CSS-in-JS libraries need a way to share styles between components, and the Context API is the most efficient way to do this. However, there is no native way in React to replicate this behavior in Server Components.
Conclusion
After more than 1 year and a half of the release of React Server Components, there are still no CSS-in-JS libraries that support Server Components and provide all the features that we had access to in the main CSS-in-JS libraries. However, this is still an ever-evolving topic.
There is a repository that proposes a CSS-in-JS tool of "dreams", which discusses some alternatives to solve the Context Api dependency problem. Although the repository author has not reached 100% usable and concrete results, the discussion is very interesting and shows evolutions about this problem, using other React APIs for this.
The repository also discusses other tools like Linaria and PandaCSS that are CSS-in-JS tools that do not require Javascript at runtime and are compatible with RSC. It's worth checking out.