diff --git a/.changeset/mean-sloths-wonder.md b/.changeset/mean-sloths-wonder.md
new file mode 100644
index 0000000000..80cbb3bfab
--- /dev/null
+++ b/.changeset/mean-sloths-wonder.md
@@ -0,0 +1,5 @@
+---
+'@leafygreen-ui/emotion': minor
+---
+
+This update exports a CacheProvider component for use in Next.JS applications
diff --git a/packages/emotion/README.md b/packages/emotion/README.md
index 065fba73a8..57683e8fa5 100644
--- a/packages/emotion/README.md
+++ b/packages/emotion/README.md
@@ -47,3 +47,244 @@ import App from './App';
const html = renderStylesToString(renderToString());
```
+
+## SSR Compatibility
+
+Emotion generates styles at runtime and injects them into the DOM. For server-side rendering, styles must be extracted during rendering and inserted into the HTML before it's sent to the client. Without proper configuration, you'll see a flash of unstyled content (FOUC).
+
+> ⚠️ **Important:**
+>
+> - Emotion does not [currently support React Server Components](https://github.com/emotion-js/emotion/issues/2978). You must use `'use client'` directive in Next.js.
+> - Ensure you're using the latest version of any `emotion` packages alongside this package.
+> - LeafyGreen UI components may require additional configuration beyond what's documented here.
+
+### Framework Guides
+
+- [Next.js (App Router)](#nextjs-app-router)
+- [Next.js (Pages Router)](#nextjs-pages-router)
+- [React Router v7+](#react-router-v7)
+- [Gatsby.js](#gatsbyjs)
+
+---
+
+### Next.js (App Router)
+
+#### 1. Create the Emotion Registry
+
+Create a new file at `src/app/EmotionRegistry.tsx`:
+
+```jsx
+'use client';
+
+import { useServerInsertedHTML } from 'next/navigation';
+import { cache, CacheProvider } from '@leafygreen-ui/emotion';
+
+export default function EmotionRegistry({
+ children,
+}: {
+ children: React.ReactNode,
+}) {
+ useServerInsertedHTML(() => {
+ const names = Object.keys(cache.inserted);
+ if (names.length === 0) return null;
+
+ let styles = '';
+ for (const name of names) {
+ const style = cache.inserted[name];
+ if (typeof style === 'string') {
+ styles += style;
+ }
+ }
+
+ return (
+
+ );
+ });
+
+ return {children};
+}
+```
+
+#### 2. Add the Registry to Your Root Layout
+
+Wrap your application in `src/app/layout.tsx`:
+
+```tsx
+import type { Metadata } from 'next';
+import EmotionRegistry from './EmotionRegistry';
+
+export const metadata: Metadata = {
+ title: 'My App',
+ description: 'My application description',
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+ );
+}
+```
+
+#### 3. Use in Client Components
+
+> The `css` function only works in **Client Components**:
+
+```tsx
+'use client';
+
+import { css } from '@leafygreen-ui/emotion';
+
+export default function MyComponent() {
+ return (
+
+ Hello World
+
+ );
+}
+```
+
+---
+
+### Next.js (Pages Router)
+
+Add Emotion's critical CSS extraction to your `_document` file:
+
+```tsx
+import { extractCritical } from '@leafygreen-ui/emotion';
+
+export default class AppDocument extends Document {
+ static async getInitialProps(
+ ctx: DocumentContext,
+ ): Promise {
+ const initialProps = await Document.getInitialProps(ctx);
+ const { css, ids } = extractCritical(initialProps.html || '');
+
+ return {
+ ...initialProps,
+ styles: (
+ <>
+ {initialProps.styles}
+
+ >
+ ),
+ };
+ // ...
+ }
+}
+```
+
+---
+
+### React Router v7+
+
+This guide covers [Framework mode](https://reactrouter.com/start/modes#framework) for React Router.
+
+#### 1. Configure Server Entry
+
+```tsx
+Update `entry.server.tsx`:
+
+import { PassThrough } from 'node:stream';
+import type { EntryContext } from 'react-router';
+import { createReadableStreamFromReadable } from '@react-router/node';
+import { ServerRouter } from 'react-router';
+import { renderToPipeableStream } from 'react-dom/server';
+import { cache, extractCritical, CacheProvider } from '@leafygreen-ui/emotion';
+
+const ABORT_DELAY = 5_000;
+
+export default function handleRequest(
+request: Request,
+responseStatusCode: number,
+responseHeaders: Headers,
+routerContext: EntryContext,
+) {
+ return new Promise((resolve, reject) => {
+ let statusCode = responseStatusCode;
+ const chunks: Buffer[] = [];
+
+ const { pipe, abort } = renderToPipeableStream(
+
+
+ ,
+ );
+
+ const collectStream = new PassThrough();
+ collectStream.on('data', chunk => chunks.push(chunk));
+
+ collectStream.on('end', () => {
+ const html = Buffer.concat(chunks).toString('utf-8');
+ const { css, ids } = extractCritical(html);
+ const emotionStyleTag = ``;
+ const htmlWithStyles = html.replace('', `${emotionStyleTag}`);
+
+ const body = new PassThrough();
+ const stream = createReadableStreamFromReadable(body);
+
+ responseHeaders.set('Content-Type', 'text/html');
+ resolve(
+ new Response(stream, {
+ headers: responseHeaders,
+ status: statusCode,
+ }),
+ );
+
+ body.write(htmlWithStyles);
+ body.end();
+ });
+
+ collectStream.on('error', reject);
+ pipe(collectStream);
+ setTimeout(abort, ABORT_DELAY);
+
+ });
+}
+```
+
+#### 2. Configure Client Entry
+
+Update `entry.client.tsx`:
+
+```tsx
+import { startTransition, StrictMode } from 'react';
+import { hydrateRoot } from 'react-dom/client';
+import { HydratedRouter } from 'react-router/dom';
+import { CacheProvider, cache } from '@leafygreen-ui/emotion';
+
+startTransition(() => {
+ hydrateRoot(
+ document,
+
+
+
+
+ ,
+ );
+});
+```
+
+---
+
+### Gatsby.js
+
+> ⚠️ **Not Currently Supported**
+>
+> There is a peer dependency mismatch between `@leafygreen-ui/emotion` and `gatsby-plugin-emotion`. As a result, we do not currently support GatsbyJS projects out of the box. If you need Emotion in a Gatsby project, refer to the [Gatsby Emotion documentation](https://www.gatsbyjs.com/docs/how-to/styling/emotion/).
diff --git a/packages/emotion/package.json b/packages/emotion/package.json
index 65a431b2f0..df9004947a 100644
--- a/packages/emotion/package.json
+++ b/packages/emotion/package.json
@@ -18,7 +18,8 @@
},
"dependencies": {
"@emotion/css": "^11.1.3",
- "@emotion/server": "^11.4.0"
+ "@emotion/server": "^11.4.0",
+ "@emotion/react": "^11.14.0"
},
"devDependencies": {
"@lg-tools/build": "workspace:^",
diff --git a/packages/emotion/src/index.ts b/packages/emotion/src/index.ts
index fa4f68eaec..aa0aa2ccc4 100644
--- a/packages/emotion/src/index.ts
+++ b/packages/emotion/src/index.ts
@@ -1,3 +1,4 @@
+import { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
import emotion from './emotion';
@@ -15,6 +16,8 @@ export const {
cache,
} = emotion;
+export { CacheProvider };
+
export const {
extractCritical,
renderStylesToString,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d8c16ffb22..e82aeaa95b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -619,7 +619,7 @@ importers:
devDependencies:
'@emotion/styled':
specifier: ^11.10.5
- version: 11.14.1(@emotion/react@11.11.1(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
+ version: 11.14.1(@emotion/react@11.14.0(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
'@lg-tools/build':
specifier: workspace:^
version: link:../../tools/build
@@ -1470,7 +1470,7 @@ importers:
devDependencies:
'@emotion/styled':
specifier: ^11.10.5
- version: 11.14.1(@emotion/react@11.11.1(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
+ version: 11.14.1(@emotion/react@11.14.0(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
'@lg-tools/build':
specifier: workspace:^
version: link:../../tools/build
@@ -1802,6 +1802,9 @@ importers:
'@emotion/css':
specifier: ^11.1.3
version: 11.9.0(@babel/core@7.28.0)
+ '@emotion/react':
+ specifier: ^11.14.0
+ version: 11.14.0(@types/react@18.2.23)(react@18.3.1)
'@emotion/server':
specifier: ^11.4.0
version: 11.11.0(@emotion/css@11.9.0(@babel/core@7.28.0))
@@ -2330,7 +2333,7 @@ importers:
devDependencies:
'@emotion/styled':
specifier: ^11.10.5
- version: 11.14.1(@emotion/react@11.11.1(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
+ version: 11.14.1(@emotion/react@11.14.0(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
'@lg-tools/build':
specifier: workspace:^
version: link:../../tools/build
@@ -2746,7 +2749,7 @@ importers:
devDependencies:
'@emotion/styled':
specifier: ^11.10.5
- version: 11.14.1(@emotion/react@11.11.1(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
+ version: 11.14.1(@emotion/react@11.14.0(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
'@lg-tools/build':
specifier: workspace:^
version: link:../../tools/build
@@ -3372,7 +3375,7 @@ importers:
devDependencies:
'@emotion/styled':
specifier: ^11.14.0
- version: 11.14.1(@emotion/react@11.11.1(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
+ version: 11.14.1(@emotion/react@11.14.0(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)
'@faker-js/faker':
specifier: ^8.0.0
version: 8.0.2
@@ -5542,6 +5545,9 @@ packages:
'@emotion/cache@11.11.0':
resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==}
+ '@emotion/cache@11.14.0':
+ resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
+
'@emotion/css@11.9.0':
resolution: {integrity: sha512-S9UjCxSrxEHawOLnWw4upTwfYKb0gVQdatHejn3W9kPyXxmKv3HmjVfJ84kDLmdX8jR20OuDQwaJ4Um24qD9vA==}
peerDependencies:
@@ -5577,6 +5583,15 @@ packages:
'@types/react':
optional: true
+ '@emotion/react@11.14.0':
+ resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: '>=16.8.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@emotion/serialize@1.3.3':
resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
@@ -5615,6 +5630,9 @@ packages:
'@emotion/weak-memoize@0.3.1':
resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==}
+ '@emotion/weak-memoize@0.4.0':
+ resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
+
'@esbuild/aix-ppc64@0.25.8':
resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
engines: {node: '>=18'}
@@ -13741,6 +13759,14 @@ snapshots:
'@emotion/weak-memoize': 0.3.1
stylis: 4.2.0
+ '@emotion/cache@11.14.0':
+ dependencies:
+ '@emotion/memoize': 0.9.0
+ '@emotion/sheet': 1.4.0
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.4.0
+ stylis: 4.2.0
+
'@emotion/css@11.9.0(@babel/core@7.24.3)':
dependencies:
'@emotion/babel-plugin': 11.11.0
@@ -13799,6 +13825,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@emotion/react@11.14.0(@types/react@18.2.23)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.24.1
+ '@emotion/babel-plugin': 11.13.5
+ '@emotion/cache': 11.14.0
+ '@emotion/serialize': 1.3.3
+ '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.4.0
+ hoist-non-react-statics: 3.3.2
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.2.23
+ transitivePeerDependencies:
+ - supports-color
+
'@emotion/serialize@1.3.3':
dependencies:
'@emotion/hash': 0.9.2
@@ -13827,12 +13869,12 @@ snapshots:
'@emotion/sheet@1.4.0': {}
- '@emotion/styled@11.14.1(@emotion/react@11.11.1(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)':
+ '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.2.23)(react@18.3.1))(@types/react@18.2.23)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.24.1
'@emotion/babel-plugin': 11.13.5
'@emotion/is-prop-valid': 1.3.1
- '@emotion/react': 11.11.1(@types/react@18.2.23)(react@18.3.1)
+ '@emotion/react': 11.14.0(@types/react@18.2.23)(react@18.3.1)
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
'@emotion/utils': 1.4.2
@@ -13852,6 +13894,8 @@ snapshots:
'@emotion/weak-memoize@0.3.1': {}
+ '@emotion/weak-memoize@0.4.0': {}
+
'@esbuild/aix-ppc64@0.25.8':
optional: true