ssr-nextjs
MUI server-side rendering with Next.js — App Router, Pages Router, RSC compatibility, Emotion cache, and Pigment CSS
What this skill does
# MUI SSR with Next.js
## 1. Next.js App Router Setup (v13+)
The App Router requires a client-side ThemeRegistry component that flushes Emotion's
server-generated styles into the document head via `useServerInsertedHTML`.
### ThemeRegistry client component
```tsx
// src/components/ThemeRegistry/EmotionCache.tsx
'use client';
import * as React from 'react';
import createCache from '@emotion/cache';
import { useServerInsertedHTML } from 'next/navigation';
import { CacheProvider } from '@emotion/react';
export default function NextAppDirEmotionCacheProvider(
props: { options: Parameters<typeof createCache>[0]; children: React.ReactNode }
) {
const { options, children } = props;
const [registry] = React.useState(() => {
const cache = createCache(options);
cache.compat = true;
const prevInsert = cache.insert;
let inserted: { name: string; isGlobal: boolean }[] = [];
cache.insert = (...args) => {
const [selector, serialized] = args;
if (cache.inserted[serialized.name] === undefined) {
inserted.push({
name: serialized.name,
isGlobal: !selector,
});
}
return prevInsert(...args);
};
return { cache, flush: () => { const prev = inserted; inserted = []; return prev; } };
});
useServerInsertedHTML(() => {
const names = registry.flush();
if (names.length === 0) return null;
let styles = '';
let dataEmotionAttribute = registry.cache.key;
const globals: { name: string; style: string }[] = [];
for (const { name, isGlobal } of names) {
const style = registry.cache.inserted[name];
if (typeof style === 'string') {
if (isGlobal) {
globals.push({ name, style });
} else {
styles += style;
dataEmotionAttribute += ` ${name}`;
}
}
}
return (
<>
{globals.map(({ name, style }) => (
<style
key={name}
data-emotion={`${registry.cache.key}-global`}
dangerouslySetInnerHTML={{ __html: style }}
/>
))}
{styles && (
<style
data-emotion={dataEmotionAttribute}
dangerouslySetInnerHTML={{ __html: styles }}
/>
)}
</>
);
});
return <CacheProvider value={registry.cache}>{children}</CacheProvider>;
}
```
### ThemeRegistry wrapper
```tsx
// src/components/ThemeRegistry/ThemeRegistry.tsx
'use client';
import * as React from 'react';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import NextAppDirEmotionCacheProvider from './EmotionCache';
import theme from './theme';
export default function ThemeRegistry({ children }: { children: React.ReactNode }) {
return (
<NextAppDirEmotionCacheProvider options={{ key: 'mui', prepend: true }}>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</NextAppDirEmotionCacheProvider>
);
}
```
### app/layout.tsx integration
```tsx
// app/layout.tsx
import ThemeRegistry from '@/components/ThemeRegistry/ThemeRegistry';
export const metadata = {
title: 'My App',
description: 'MUI + Next.js App Router',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<ThemeRegistry>{children}</ThemeRegistry>
</body>
</html>
);
}
```
### Required dependencies
```bash
npm install @mui/material @emotion/react @emotion/styled @emotion/cache
```
---
## 2. Next.js Pages Router Setup
The Pages Router uses `_document.tsx` to extract critical CSS at render time and
inject it into the initial HTML response.
### createEmotionCache utility
```tsx
// src/createEmotionCache.ts
import createCache from '@emotion/cache';
export default function createEmotionCache() {
return createCache({ key: 'css', prepend: true });
}
```
### _app.tsx with CacheProvider
```tsx
// pages/_app.tsx
import * as React from 'react';
import Head from 'next/head';
import { AppProps } from 'next/app';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { CacheProvider, EmotionCache } from '@emotion/react';
import theme from '../src/theme';
import createEmotionCache from '../src/createEmotionCache';
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
export default function MyApp(props: MyAppProps) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
```
### _document.tsx with extractCriticalToChunks
```tsx
// pages/_document.tsx
import * as React from 'react';
import Document, {
Html,
Head,
Main,
NextScript,
DocumentProps,
DocumentContext,
} from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import { AppType } from 'next/app';
import theme from '../src/theme';
import createEmotionCache from '../src/createEmotionCache';
interface MyDocumentProps extends DocumentProps {
emotionStyleTags: React.ReactElement[];
}
export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
return (
<Html lang="en">
<Head>
<meta name="theme-color" content={theme.palette.primary.main} />
<link rel="shortcut icon" href="/favicon.ico" />
{emotionStyleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
MyDocument.getInitialProps = async (ctx: DocumentContext) => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App: React.ComponentType<React.ComponentProps<AppType> & { emotionCache: ReturnType<typeof createEmotionCache> }>) =>
function EnhanceApp(props) {
return <App emotionCache={cache} {...props} />;
},
});
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map((style) => (
<style
data-emotion={`${style.key} ${style.ids.join(' ')}`}
key={style.key}
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
emotionStyleTags,
};
};
```
### Additional dependency for Pages Router
```bash
npm install @emotion/server
```
---
## 3. Server Components Compatibility
### MUI components do NOT work in React Server Components
Every MUI component uses React context (ThemeProvider), hooks (useTheme, useState),
or event handlers. **None of them can be rendered as RSC.** Any file importing from
`@mui/material` must include `'use client'` at the top, or be imported from a file
that does.
### Client boundary patterns
**Pattern 1: Thin client wrapper around server data**
```tsx
// app/users/page.tsx (Server Component — fetches data)
import UserTable from './UserTable';
export default async function UsersPage() {
const users = await db.user.findMany(); // server-side data fetch
return <UserTable users={users} />; // pass plain data to client
}
```
```tsx
// app/users/UserTable.tsx (Client Component — renders MUI)
'use client';
import {
Table, TableBody, TableCell, TableContainer,
TableHead, TableRow, Paper
} from '@mui/material';
interface User { id: string; name: string; email: string; }
export default function UserTable({ users }: { users: User[] }) {
Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.