Using @emotion/styled with remix

Read time: 15 min.

Tweet this article
DJ Turntables

Photo by Daniel Eliashevskyi on Unsplash

The Setup

Start by creating a fresh remix app. Let's call it remix-with-emotion.

$> npx create-remix@latest
bash
R E M I X
💿 Welcome to Remix! Let's get you set up with a new project.
? Where would you like to create your app? remix-with-emotion
? Where do you want to deploy? Choose Remix if you're unsure, it's easy to change deployment targets. Remix App Server
? TypeScript or JavaScript? TypeScript
? Do you want me to run `npm install`? Yes

Then cd into the new project directory. After we have changed into the project directory we need to install 4 @emotion packages that will let @emotion work with the remix server.

$> npm i --save @emotion/styled @emotion/react @emotion/server @emotion/cache

The Configuration

We need to start by adding a special string (__STYLES__) to the head of the document in root.tsx when document is undefined. Remix renders pages server side, before document is defined. We will use this fact to replace __STYLES__ with style tags created by @emotion in entry.server.tsx before sending the final page to the browser.

app/root.tsx
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration
} from "remix";
import type { MetaFunction } from "remix";
export const meta: MetaFunction = () => {
return { title: "New Remix App" };
};
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
{typeof document === "undefined"
? "__STYLES__"
: null}
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</html>
);
}

Then in entry.server.tsx we create a cache and the emotion server using that cache. We wrap the RemixServer component in the cache provider so that emotion can cache some of the styles. Last we extract the styles, construct the style tags, and replace __STYLES__ with those tags.

app/entry.server.tsx
import { renderToString } from "react-dom/server";
import { RemixServer } from "remix";
import type { EntryContext } from "remix";
import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
const key = 'custom';
const cache = createCache({ key });
const {
extractCriticalToChunks,
constructStyleTagsFromChunks,
} = createEmotionServer(cache);
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
let markup = renderToString(
<CacheProvider value={cache}>
<RemixServer context={remixContext} url={request.url} />
</CacheProvider>
);
const chunks = extractCriticalToChunks(markup);
const styles = constructStyleTagsFromChunks(chunks);
markup = markup.replace('__STYLES__', styles);
responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders
});
}

Try It Out

That should do it, go ahead and try it out. Create an @emotion/styled component in your main index route by changing the default h1 tag to something using styled.

app/routes/index.tsx
import styled from '@emotion/styled';
const MyStyledH1 = styled.h1`
font-size: 5rem;
color: green;
`;
export default function Index() {
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
<MyStyledH1>Welcome to Remix</MyStyledH1>
<ul>
<li>
<a
target="_blank"
href="https://remix.run/tutorials/blog"
rel="noreferrer"
>
Quickstart Blog Tutorial
</a>
</li>
{/* ... */}
</ul>
</div>
);
}

External Resources