Using @emotion/styled with remix
Read time: 15 min.
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
bashR 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.tsximport {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.tsximport { 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.tsximport 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><atarget="_blank"href="https://remix.run/tutorials/blog"rel="noreferrer">Quickstart Blog Tutorial</a></li>{/* ... */}</ul></div>);}