0
# Next.js SSR Support
1
2
TSS-React provides comprehensive server-side rendering utilities for Next.js applications, supporting both the modern App Router and traditional Pages Router patterns. The integration ensures proper CSS extraction, hydration, and cache management for optimal performance.
3
4
## Capabilities
5
6
### App Router Support (Next.js 13+)
7
8
Emotion cache provider specifically designed for Next.js App Router with `useServerInsertedHTML` integration.
9
10
```typescript { .api }
11
/**
12
* Emotion cache provider for Next.js App Router
13
* @param props - Configuration props for cache provider
14
* @returns JSX element providing emotion cache to component tree
15
*/
16
function NextAppDirEmotionCacheProvider(
17
props: NextAppDirEmotionCacheProviderProps
18
): JSX.Element;
19
20
interface NextAppDirEmotionCacheProviderProps {
21
/** Options passed to createCache() from @emotion/cache */
22
options: Omit<OptionsOfCreateCache, "insertionPoint"> & {
23
/** Whether to prepend styles with @layer emotion for CSS cascade control */
24
prepend?: boolean;
25
};
26
/** Custom CacheProvider component, defaults to @emotion/react CacheProvider */
27
CacheProvider?: React.Provider<EmotionCache>;
28
/** Child components */
29
children: ReactNode;
30
}
31
32
interface OptionsOfCreateCache {
33
/** Cache key for style identification */
34
key: string;
35
/** Nonce for Content Security Policy */
36
nonce?: string;
37
/** Style container element */
38
container?: HTMLElement;
39
/** Whether styles are speedy (no text content) */
40
speedy?: boolean;
41
}
42
```
43
44
**Usage Examples:**
45
46
```typescript
47
// app/layout.tsx
48
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
49
50
export default function RootLayout({
51
children,
52
}: {
53
children: React.ReactNode;
54
}) {
55
return (
56
<html lang="en">
57
<body>
58
<NextAppDirEmotionCacheProvider
59
options={{
60
key: "css",
61
prepend: true // Optional: wrap styles in @layer emotion
62
}}
63
>
64
{children}
65
</NextAppDirEmotionCacheProvider>
66
</body>
67
</html>
68
);
69
}
70
71
// With custom nonce for CSP
72
export default function SecureLayout({
73
children,
74
}: {
75
children: React.ReactNode;
76
}) {
77
return (
78
<html lang="en">
79
<body>
80
<NextAppDirEmotionCacheProvider
81
options={{
82
key: "css",
83
nonce: process.env.CSP_NONCE,
84
prepend: true
85
}}
86
>
87
{children}
88
</NextAppDirEmotionCacheProvider>
89
</body>
90
</html>
91
);
92
}
93
94
// app/page.tsx - Using TSS-React in App Router
95
import { tss } from "tss-react";
96
97
const useStyles = tss.create({
98
container: {
99
padding: "2rem",
100
backgroundColor: "#f5f5f5",
101
minHeight: "100vh"
102
},
103
title: {
104
fontSize: "2rem",
105
fontWeight: "bold",
106
color: "#333",
107
marginBottom: "1rem"
108
}
109
});
110
111
export default function HomePage() {
112
const { classes } = useStyles();
113
114
return (
115
<div className={classes.container}>
116
<h1 className={classes.title}>Welcome to Next.js App Router with TSS-React</h1>
117
</div>
118
);
119
}
120
```
121
122
### Pages Router Support (Next.js 12 and earlier)
123
124
Advanced SSR approach for Next.js Pages Router with proper critical CSS extraction and hydration.
125
126
```typescript { .api }
127
/**
128
* Creates SSR utilities for Next.js Pages Router
129
* @param options - Emotion cache options
130
* @param CacheProvider - Optional custom cache provider component
131
* @returns Object containing App and Document enhancement functions
132
*/
133
function createEmotionSsrAdvancedApproach(
134
options: Omit<OptionsOfCreateCache, "insertionPoint"> & {
135
/** Whether to prepend styles for CSS cascade control */
136
prepend?: boolean;
137
},
138
CacheProvider?: Function
139
): {
140
/**
141
* Higher-order component for _app.js
142
* @param App - Next.js App component
143
* @returns Enhanced App component with emotion cache
144
*/
145
withAppEmotionCache<AppComponent extends NextComponentType<any, any, any>>(
146
App: AppComponent
147
): AppComponent;
148
149
/**
150
* Augments _document.js for SSR critical CSS extraction
151
* @param Document - Next.js Document component
152
*/
153
augmentDocumentWithEmotionCache(
154
Document: NextComponentType<any, any, any>
155
): void;
156
};
157
158
type NextComponentType<Context = any, InitialProps = {}, Props = {}> = ComponentType<Props> & {
159
getInitialProps?(context: Context): InitialProps | Promise<InitialProps>;
160
};
161
```
162
163
**Usage Examples:**
164
165
```typescript
166
// utils/ssr.ts
167
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";
168
169
export const { withAppEmotionCache, augmentDocumentWithEmotionCache } =
170
createEmotionSsrAdvancedApproach({
171
key: "css",
172
prepend: true // Optional: control CSS precedence
173
});
174
175
// pages/_app.tsx
176
import type { AppProps } from "next/app";
177
import { withAppEmotionCache } from "../utils/ssr";
178
179
function MyApp({ Component, pageProps }: AppProps) {
180
return <Component {...pageProps} />;
181
}
182
183
export default withAppEmotionCache(MyApp);
184
185
// pages/_document.tsx
186
import Document, { Html, Head, Main, NextScript } from "next/document";
187
import { augmentDocumentWithEmotionCache } from "../utils/ssr";
188
189
augmentDocumentWithEmotionCache(Document);
190
191
export default class MyDocument extends Document {
192
render() {
193
return (
194
<Html lang="en">
195
<Head />
196
<body>
197
<Main />
198
<NextScript />
199
</body>
200
</Html>
201
);
202
}
203
}
204
205
// pages/index.tsx - Using TSS-React in Pages Router
206
import { tss } from "tss-react";
207
208
const useStyles = tss.create({
209
container: {
210
padding: "2rem",
211
backgroundColor: "#f0f8ff",
212
minHeight: "100vh"
213
},
214
title: {
215
fontSize: "2.5rem",
216
fontWeight: "700",
217
color: "#1a365d",
218
textAlign: "center",
219
marginBottom: "2rem"
220
},
221
card: {
222
backgroundColor: "white",
223
borderRadius: "8px",
224
padding: "1.5rem",
225
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
226
maxWidth: "600px",
227
margin: "0 auto"
228
}
229
});
230
231
export default function HomePage() {
232
const { classes } = useStyles();
233
234
return (
235
<div className={classes.container}>
236
<h1 className={classes.title}>Pages Router with TSS-React</h1>
237
<div className={classes.card}>
238
<p>This page demonstrates server-side rendering with critical CSS extraction.</p>
239
</div>
240
</div>
241
);
242
}
243
```
244
245
### MUI Integration with Next.js
246
247
Combining MUI theme support with Next.js SSR for complete styling solutions.
248
249
```typescript
250
// App Router with MUI (app/layout.tsx)
251
import { ThemeProvider, createTheme } from "@mui/material/styles";
252
import CssBaseline from "@mui/material/CssBaseline";
253
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
254
255
const theme = createTheme({
256
palette: {
257
mode: "light",
258
primary: { main: "#1976d2" },
259
secondary: { main: "#dc004e" }
260
}
261
});
262
263
export default function RootLayout({
264
children,
265
}: {
266
children: React.ReactNode;
267
}) {
268
return (
269
<html lang="en">
270
<body>
271
<NextAppDirEmotionCacheProvider options={{ key: "css" }}>
272
<ThemeProvider theme={theme}>
273
<CssBaseline />
274
{children}
275
</ThemeProvider>
276
</NextAppDirEmotionCacheProvider>
277
</body>
278
</html>
279
);
280
}
281
282
// Pages Router with MUI
283
// utils/mui-ssr.ts
284
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";
285
286
export const { withAppEmotionCache, augmentDocumentWithEmotionCache } =
287
createEmotionSsrAdvancedApproach({ key: "css" });
288
289
// pages/_app.tsx
290
import type { AppProps } from "next/app";
291
import { ThemeProvider, createTheme } from "@mui/material/styles";
292
import CssBaseline from "@mui/material/CssBaseline";
293
import { withAppEmotionCache } from "../utils/mui-ssr";
294
295
const theme = createTheme({
296
palette: {
297
primary: { main: "#2196f3" },
298
secondary: { main: "#f50057" }
299
}
300
});
301
302
function MyApp({ Component, pageProps }: AppProps) {
303
return (
304
<ThemeProvider theme={theme}>
305
<CssBaseline />
306
<Component {...pageProps} />
307
</ThemeProvider>
308
);
309
}
310
311
export default withAppEmotionCache(MyApp);
312
313
// Component using MUI integration
314
import { tss } from "tss-react/mui";
315
import { Button, Paper, Typography } from "@mui/material";
316
317
const useStyles = tss.create(({ theme }) => ({
318
paper: {
319
padding: theme.spacing(3),
320
margin: theme.spacing(2),
321
backgroundColor: theme.palette.background.paper,
322
borderRadius: theme.shape.borderRadius
323
},
324
title: {
325
color: theme.palette.primary.main,
326
marginBottom: theme.spacing(2)
327
},
328
button: {
329
marginTop: theme.spacing(2),
330
backgroundImage: `linear-gradient(45deg, ${theme.palette.primary.main} 30%, ${theme.palette.secondary.main} 90%)`,
331
color: "white"
332
}
333
}));
334
335
function MuiComponent() {
336
const { classes } = useStyles();
337
338
return (
339
<Paper className={classes.paper}>
340
<Typography variant="h4" className={classes.title}>
341
MUI + TSS-React + Next.js
342
</Typography>
343
<Typography variant="body1">
344
This demonstrates the complete integration of MUI theming with TSS-React in Next.js SSR.
345
</Typography>
346
<Button variant="contained" className={classes.button}>
347
Styled Button
348
</Button>
349
</Paper>
350
);
351
}
352
```
353
354
### Advanced SSR Configuration
355
356
#### Custom Cache Configuration
357
358
```typescript
359
import createCache from "@emotion/cache";
360
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";
361
362
// Custom cache with specific configuration
363
const customCache = createCache({
364
key: "my-app",
365
nonce: process.env.CSP_NONCE,
366
speedy: process.env.NODE_ENV === "production"
367
});
368
369
export const { withAppEmotionCache, augmentDocumentWithEmotionCache } =
370
createEmotionSsrAdvancedApproach(
371
{
372
key: "my-app",
373
nonce: process.env.CSP_NONCE,
374
prepend: true
375
}
376
);
377
```
378
379
#### Content Security Policy Integration
380
381
```typescript
382
// next.config.js
383
const ContentSecurityPolicy = `
384
default-src 'self';
385
script-src 'self' 'nonce-${process.env.CSP_NONCE}';
386
style-src 'self' 'nonce-${process.env.CSP_NONCE}' 'unsafe-inline';
387
img-src 'self' data: https:;
388
`;
389
390
module.exports = {
391
async headers() {
392
return [
393
{
394
source: "/(.*)",
395
headers: [
396
{
397
key: "Content-Security-Policy",
398
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim()
399
}
400
]
401
}
402
];
403
}
404
};
405
406
// app/layout.tsx with CSP nonce
407
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
408
409
export default function RootLayout({
410
children,
411
}: {
412
children: React.ReactNode;
413
}) {
414
return (
415
<html lang="en">
416
<body>
417
<NextAppDirEmotionCacheProvider
418
options={{
419
key: "css",
420
nonce: process.env.CSP_NONCE
421
}}
422
>
423
{children}
424
</NextAppDirEmotionCacheProvider>
425
</body>
426
</html>
427
);
428
}
429
```
430
431
#### Performance Optimization
432
433
```typescript
434
// Production-optimized SSR setup
435
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";
436
437
export const { withAppEmotionCache, augmentDocumentWithEmotionCache } =
438
createEmotionSsrAdvancedApproach({
439
key: "css",
440
speedy: true, // Enable speedy mode for production
441
prepend: true, // Ensure proper CSS cascade
442
nonce: process.env.CSP_NONCE
443
});
444
445
// pages/_document.tsx with optimization
446
import Document, { Html, Head, Main, NextScript, DocumentContext } from "next/document";
447
import { augmentDocumentWithEmotionCache } from "../utils/ssr";
448
449
augmentDocumentWithEmotionCache(Document);
450
451
export default class MyDocument extends Document {
452
static async getInitialProps(ctx: DocumentContext) {
453
const initialProps = await Document.getInitialProps(ctx);
454
return initialProps;
455
}
456
457
render() {
458
return (
459
<Html lang="en">
460
<Head>
461
{/* Preload critical resources */}
462
<link rel="preconnect" href="https://fonts.googleapis.com" />
463
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
464
</Head>
465
<body>
466
<Main />
467
<NextScript />
468
</body>
469
</Html>
470
);
471
}
472
}
473
```
474
475
### Migration and Compatibility
476
477
#### From @emotion/styled SSR
478
479
```typescript
480
// Before: Manual emotion SSR setup
481
import createEmotionServer from "@emotion/server/create-instance";
482
import createCache from "@emotion/cache";
483
484
// After: TSS-React automated setup
485
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";
486
487
export const { withAppEmotionCache, augmentDocumentWithEmotionCache } =
488
createEmotionSsrAdvancedApproach({
489
key: "css",
490
prepend: true
491
});
492
```
493
494
#### Troubleshooting Common Issues
495
496
```typescript
497
// Issue: Styles not being extracted on server
498
// Solution: Ensure Document is properly augmented
499
// pages/_document.tsx
500
import Document from "next/document";
501
import { augmentDocumentWithEmotionCache } from "../utils/ssr";
502
503
// This MUST be called before the class definition
504
augmentDocumentWithEmotionCache(Document);
505
506
export default class MyDocument extends Document {
507
// Document implementation
508
}
509
510
// Issue: CSS precedence problems
511
// Solution: Use prepend option to control cascade
512
export const { withAppEmotionCache, augmentDocumentWithEmotionCache } =
513
createEmotionSsrAdvancedApproach({
514
key: "css",
515
prepend: true // Ensures TSS styles come first
516
});
517
518
// Issue: Hydration mismatches
519
// Solution: Ensure cache key consistency between server and client
520
const cacheKey = "tss-react-css";
521
522
export const { withAppEmotionCache, augmentDocumentWithEmotionCache } =
523
createEmotionSsrAdvancedApproach({
524
key: cacheKey, // Same key used on both server and client
525
prepend: true
526
});
527
```