CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tss-react

Type safe CSS-in-JS API heavily inspired by react-jss

Pending
Overview
Eval results
Files

nextjs-ssr.mddocs/

Next.js SSR Support

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.

Capabilities

App Router Support (Next.js 13+)

Emotion cache provider specifically designed for Next.js App Router with useServerInsertedHTML integration.

/**
 * Emotion cache provider for Next.js App Router
 * @param props - Configuration props for cache provider
 * @returns JSX element providing emotion cache to component tree
 */
function NextAppDirEmotionCacheProvider(
  props: NextAppDirEmotionCacheProviderProps
): JSX.Element;

interface NextAppDirEmotionCacheProviderProps {
  /** Options passed to createCache() from @emotion/cache */
  options: Omit<OptionsOfCreateCache, "insertionPoint"> & {
    /** Whether to prepend styles with @layer emotion for CSS cascade control */
    prepend?: boolean;
  };
  /** Custom CacheProvider component, defaults to @emotion/react CacheProvider */
  CacheProvider?: React.Provider<EmotionCache>;
  /** Child components */
  children: ReactNode;
}

interface OptionsOfCreateCache {
  /** Cache key for style identification */
  key: string;
  /** Nonce for Content Security Policy */
  nonce?: string;
  /** Style container element */
  container?: HTMLElement;
  /** Whether styles are speedy (no text content) */
  speedy?: boolean;
}

Usage Examples:

// app/layout.tsx
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <NextAppDirEmotionCacheProvider
          options={{
            key: "css",
            prepend: true // Optional: wrap styles in @layer emotion
          }}
        >
          {children}
        </NextAppDirEmotionCacheProvider>
      </body>
    </html>
  );
}

// With custom nonce for CSP
export default function SecureLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <NextAppDirEmotionCacheProvider
          options={{
            key: "css",
            nonce: process.env.CSP_NONCE,
            prepend: true
          }}
        >
          {children}
        </NextAppDirEmotionCacheProvider>
      </body>
    </html>
  );
}

// app/page.tsx - Using TSS-React in App Router
import { tss } from "tss-react";

const useStyles = tss.create({
  container: {
    padding: "2rem",
    backgroundColor: "#f5f5f5",
    minHeight: "100vh"
  },
  title: {
    fontSize: "2rem",
    fontWeight: "bold",
    color: "#333",
    marginBottom: "1rem"
  }
});

export default function HomePage() {
  const { classes } = useStyles();
  
  return (
    <div className={classes.container}>
      <h1 className={classes.title}>Welcome to Next.js App Router with TSS-React</h1>
    </div>
  );
}

Pages Router Support (Next.js 12 and earlier)

Advanced SSR approach for Next.js Pages Router with proper critical CSS extraction and hydration.

/**
 * Creates SSR utilities for Next.js Pages Router
 * @param options - Emotion cache options
 * @param CacheProvider - Optional custom cache provider component
 * @returns Object containing App and Document enhancement functions
 */
function createEmotionSsrAdvancedApproach(
  options: Omit<OptionsOfCreateCache, "insertionPoint"> & {
    /** Whether to prepend styles for CSS cascade control */
    prepend?: boolean;
  },
  CacheProvider?: Function
): {
  /**
   * Higher-order component for _app.js
   * @param App - Next.js App component
   * @returns Enhanced App component with emotion cache
   */
  withAppEmotionCache<AppComponent extends NextComponentType<any, any, any>>(
    App: AppComponent
  ): AppComponent;
  
  /**
   * Augments _document.js for SSR critical CSS extraction
   * @param Document - Next.js Document component
   */
  augmentDocumentWithEmotionCache(
    Document: NextComponentType<any, any, any>
  ): void;
};

type NextComponentType<Context = any, InitialProps = {}, Props = {}> = ComponentType<Props> & {
  getInitialProps?(context: Context): InitialProps | Promise<InitialProps>;
};

Usage Examples:

// utils/ssr.ts
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";

export const { withAppEmotionCache, augmentDocumentWithEmotionCache } = 
  createEmotionSsrAdvancedApproach({
    key: "css",
    prepend: true // Optional: control CSS precedence
  });

// pages/_app.tsx
import type { AppProps } from "next/app";
import { withAppEmotionCache } from "../utils/ssr";

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default withAppEmotionCache(MyApp);

// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from "next/document";
import { augmentDocumentWithEmotionCache } from "../utils/ssr";

augmentDocumentWithEmotionCache(Document);

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// pages/index.tsx - Using TSS-React in Pages Router
import { tss } from "tss-react";

const useStyles = tss.create({
  container: {
    padding: "2rem",
    backgroundColor: "#f0f8ff",
    minHeight: "100vh"
  },
  title: {
    fontSize: "2.5rem",
    fontWeight: "700",
    color: "#1a365d",
    textAlign: "center",
    marginBottom: "2rem"
  },
  card: {
    backgroundColor: "white",
    borderRadius: "8px",
    padding: "1.5rem",
    boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
    maxWidth: "600px",
    margin: "0 auto"
  }
});

export default function HomePage() {
  const { classes } = useStyles();
  
  return (
    <div className={classes.container}>
      <h1 className={classes.title}>Pages Router with TSS-React</h1>
      <div className={classes.card}>
        <p>This page demonstrates server-side rendering with critical CSS extraction.</p>
      </div>
    </div>
  );
}

MUI Integration with Next.js

Combining MUI theme support with Next.js SSR for complete styling solutions.

// App Router with MUI (app/layout.tsx)
import { ThemeProvider, createTheme } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";

const theme = createTheme({
  palette: {
    mode: "light",
    primary: { main: "#1976d2" },
    secondary: { main: "#dc004e" }
  }
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <NextAppDirEmotionCacheProvider options={{ key: "css" }}>
          <ThemeProvider theme={theme}>
            <CssBaseline />
            {children}
          </ThemeProvider>
        </NextAppDirEmotionCacheProvider>
      </body>
    </html>
  );
}

// Pages Router with MUI
// utils/mui-ssr.ts
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";

export const { withAppEmotionCache, augmentDocumentWithEmotionCache } = 
  createEmotionSsrAdvancedApproach({ key: "css" });

// pages/_app.tsx
import type { AppProps } from "next/app";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import { withAppEmotionCache } from "../utils/mui-ssr";

const theme = createTheme({
  palette: {
    primary: { main: "#2196f3" },
    secondary: { main: "#f50057" }
  }
});

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

export default withAppEmotionCache(MyApp);

// Component using MUI integration
import { tss } from "tss-react/mui";
import { Button, Paper, Typography } from "@mui/material";

const useStyles = tss.create(({ theme }) => ({
  paper: {
    padding: theme.spacing(3),
    margin: theme.spacing(2),
    backgroundColor: theme.palette.background.paper,
    borderRadius: theme.shape.borderRadius
  },
  title: {
    color: theme.palette.primary.main,
    marginBottom: theme.spacing(2)
  },
  button: {
    marginTop: theme.spacing(2),
    backgroundImage: `linear-gradient(45deg, ${theme.palette.primary.main} 30%, ${theme.palette.secondary.main} 90%)`,
    color: "white"
  }
}));

function MuiComponent() {
  const { classes } = useStyles();
  
  return (
    <Paper className={classes.paper}>
      <Typography variant="h4" className={classes.title}>
        MUI + TSS-React + Next.js
      </Typography>
      <Typography variant="body1">
        This demonstrates the complete integration of MUI theming with TSS-React in Next.js SSR.
      </Typography>
      <Button variant="contained" className={classes.button}>
        Styled Button
      </Button>
    </Paper>
  );
}

Advanced SSR Configuration

Custom Cache Configuration

import createCache from "@emotion/cache";
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";

// Custom cache with specific configuration
const customCache = createCache({
  key: "my-app",
  nonce: process.env.CSP_NONCE,
  speedy: process.env.NODE_ENV === "production"
});

export const { withAppEmotionCache, augmentDocumentWithEmotionCache } = 
  createEmotionSsrAdvancedApproach(
    {
      key: "my-app",
      nonce: process.env.CSP_NONCE,
      prepend: true
    }
  );

Content Security Policy Integration

// next.config.js
const ContentSecurityPolicy = `
  default-src 'self';
  script-src 'self' 'nonce-${process.env.CSP_NONCE}';
  style-src 'self' 'nonce-${process.env.CSP_NONCE}' 'unsafe-inline';
  img-src 'self' data: https:;
`;

module.exports = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          {
            key: "Content-Security-Policy",
            value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim()
          }
        ]
      }
    ];
  }
};

// app/layout.tsx with CSP nonce
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <NextAppDirEmotionCacheProvider
          options={{
            key: "css",
            nonce: process.env.CSP_NONCE
          }}
        >
          {children}
        </NextAppDirEmotionCacheProvider>
      </body>
    </html>
  );
}

Performance Optimization

// Production-optimized SSR setup
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";

export const { withAppEmotionCache, augmentDocumentWithEmotionCache } = 
  createEmotionSsrAdvancedApproach({
    key: "css",
    speedy: true, // Enable speedy mode for production
    prepend: true, // Ensure proper CSS cascade
    nonce: process.env.CSP_NONCE
  });

// pages/_document.tsx with optimization
import Document, { Html, Head, Main, NextScript, DocumentContext } from "next/document";
import { augmentDocumentWithEmotionCache } from "../utils/ssr";

augmentDocumentWithEmotionCache(Document);

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx);
    return initialProps;
  }

  render() {
    return (
      <Html lang="en">
        <Head>
          {/* Preload critical resources */}
          <link rel="preconnect" href="https://fonts.googleapis.com" />
          <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Migration and Compatibility

From @emotion/styled SSR

// Before: Manual emotion SSR setup
import createEmotionServer from "@emotion/server/create-instance";
import createCache from "@emotion/cache";

// After: TSS-React automated setup
import { createEmotionSsrAdvancedApproach } from "tss-react/next/pagesDir";

export const { withAppEmotionCache, augmentDocumentWithEmotionCache } = 
  createEmotionSsrAdvancedApproach({
    key: "css",
    prepend: true
  });

Troubleshooting Common Issues

// Issue: Styles not being extracted on server
// Solution: Ensure Document is properly augmented
// pages/_document.tsx
import Document from "next/document";
import { augmentDocumentWithEmotionCache } from "../utils/ssr";

// This MUST be called before the class definition
augmentDocumentWithEmotionCache(Document);

export default class MyDocument extends Document {
  // Document implementation
}

// Issue: CSS precedence problems
// Solution: Use prepend option to control cascade
export const { withAppEmotionCache, augmentDocumentWithEmotionCache } = 
  createEmotionSsrAdvancedApproach({
    key: "css",
    prepend: true // Ensures TSS styles come first
  });

// Issue: Hydration mismatches
// Solution: Ensure cache key consistency between server and client
const cacheKey = "tss-react-css";

export const { withAppEmotionCache, augmentDocumentWithEmotionCache } = 
  createEmotionSsrAdvancedApproach({
    key: cacheKey, // Same key used on both server and client
    prepend: true
  });

Install with Tessl CLI

npx tessl i tessl/npm-tss-react

docs

compatibility.md

core-tss-api.md

css-utilities.md

dsfr-integration.md

global-styles-keyframes.md

index.md

makestyles-api.md

mui-integration.md

nextjs-ssr.md

withstyles-hoc.md

tile.json