or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-styling.mdcss-utilities.mdindex.mdreact-native.mdserver-side-rendering.mdtest-utilities.mdtheming.mdtypescript-integration.md
tile.json

server-side-rendering.mddocs/

Server-Side Rendering

styled-components provides complete server-side rendering (SSR) support with style extraction, hydration, and server-side style sheet management for production applications and SEO optimization.

ServerStyleSheet

The ServerStyleSheet class collects styles during server-side rendering and provides methods to extract them for inclusion in the HTML document.

class ServerStyleSheet {
  collectStyles(tree: React.ReactElement): React.ReactElement;
  getStyleTags(): string;
  getStyleElement(): React.ReactElement[];
  seal(): void;
  clearTag(): void;
  instance: StyleSheet;
}

Usage Examples:

Basic SSR Setup

import { ServerStyleSheet } from 'styled-components';
import { renderToString } from 'react-dom/server';

function renderApp(App) {
  const sheet = new ServerStyleSheet();
  
  try {
    // Wrap your app with collectStyles
    const html = renderToString(
      sheet.collectStyles(<App />)
    );
    
    // Extract the styles as string
    const styleTags = sheet.getStyleTags();
    
    return {
      html,
      styleTags
    };
  } catch (error) {
    console.error('SSR Error:', error);
    throw error;
  } finally {
    sheet.seal();
  }
}

// Usage in server
app.get('*', (req, res) => {
  const { html, styleTags } = renderApp(<App />);
  
  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>My App</title>
        ${styleTags}
      </head>
      <body>
        <div id="root">${html}</div>
        <script src="/bundle.js"></script>
      </body>
    </html>
  `);
});

Next.js Integration

// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props =>
            sheet.collectStyles(<App {...props} />),
        });

      const initialProps = await Document.getInitialProps(ctx);
      
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      };
    } finally {
      sheet.seal();
    }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Express.js with Streaming

import { renderToNodeStream } from 'react-dom/server';
import { ServerStyleSheet } from 'styled-components';

app.get('*', (req, res) => {
  const sheet = new ServerStyleSheet();
  
  // Send initial HTML
  res.write(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>My App</title>
      </head>
      <body>
        <div id="root">
  `);
  
  try {
    const stream = renderToNodeStream(
      sheet.collectStyles(<App />)
    );
    
    stream.pipe(res, { end: false });
    
    stream.on('end', () => {
      // Inject styles before closing body
      const styleTags = sheet.getStyleTags();
      res.write(`
        </div>
        ${styleTags}
        <script src="/bundle.js"></script>
      </body>
    </html>
      `);
      res.end();
      sheet.seal();
    });
  } catch (error) {
    console.error('Streaming SSR Error:', error);
    sheet.seal();
    res.status(500).send('Internal Server Error');
  }
});

StyleSheetManager

The StyleSheetManager component provides configuration and context for style sheet behavior, including custom targets and server-side rendering settings.

declare const StyleSheetManager: React.ComponentType<{
  sheet?: StyleSheet;
  target?: HTMLElement;
  children?: React.ReactNode;
  disableCSSOMInjection?: boolean;
  disableVendorPrefixes?: boolean;
  stylisPlugins?: StylisPlugin[];
}>;

declare const StyleSheetContext: React.Context<IStyleSheetContext>;
declare const StyleSheetConsumer: React.Consumer<IStyleSheetContext>;

interface IStyleSheetContext {
  styleSheet: StyleSheet;
  stylis: Stringifier;
}

Usage Examples:

Custom Style Target

import { StyleSheetManager } from 'styled-components';

function App() {
  const [customTarget, setCustomTarget] = React.useState(null);
  
  React.useEffect(() => {
    // Create a custom target for styles (e.g., in iframe or shadow DOM)
    const target = document.createElement('div');
    document.head.appendChild(target);
    setCustomTarget(target);
    
    return () => {
      document.head.removeChild(target);
    };
  }, []);
  
  if (!customTarget) return null;
  
  return (
    <StyleSheetManager target={customTarget}>
      <StyledComponents />
    </StyleSheetManager>
  );
}

Disable CSSOM Injection

// Useful for certain SSR scenarios or when working with CSP
function ServerApp() {
  return (
    <StyleSheetManager disableCSSOMInjection>
      <App />
    </StyleSheetManager>
  );
}

Custom Stylis Plugins

import rtlPlugin from 'stylis-plugin-rtl';

function RTLApp() {
  return (
    <StyleSheetManager stylisPlugins={[rtlPlugin]}>
      <App />
    </StyleSheetManager>
  );
}

SSR Best Practices

Style Extraction Pattern

// utils/ssr.js
import { ServerStyleSheet } from 'styled-components';

export function extractStyles(app) {
  const sheet = new ServerStyleSheet();
  
  try {
    const html = renderToString(sheet.collectStyles(app));
    const styles = sheet.getStyleElement();
    
    return { html, styles };
  } finally {
    sheet.seal();
  }
}

// Error handling wrapper
export function safeExtractStyles(app) {
  try {
    return extractStyles(app);
  } catch (error) {
    console.error('Style extraction failed:', error);
    // Return app without styles as fallback
    return {
      html: renderToString(app),
      styles: []
    };
  }
}

Hydration Helpers

// client/hydration.js
import { hydrate } from 'react-dom';

function hydrateApp() {
  const rootElement = document.getElementById('root');
  
  if (rootElement.hasChildNodes()) {
    // Server-rendered content exists, hydrate
    hydrate(<App />, rootElement);
  } else {
    // No server-rendered content, render normally
    render(<App />, rootElement);
  }
}

// Wait for stylesheets to load before hydrating
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', hydrateApp);
} else {
  hydrateApp();
}

Memory Management

// Long-running server memory management
class StyleSheetPool {
  private sheets: Map<string, ServerStyleSheet> = new Map();
  
  getSheet(requestId: string): ServerStyleSheet {
    if (!this.sheets.has(requestId)) {
      this.sheets.set(requestId, new ServerStyleSheet());
    }
    return this.sheets.get(requestId);
  }
  
  releaseSheet(requestId: string): void {
    const sheet = this.sheets.get(requestId);
    if (sheet) {
      sheet.seal();
      this.sheets.delete(requestId);
    }
  }
  
  cleanup(): void {
    for (const [id, sheet] of this.sheets) {
      sheet.seal();
    }
    this.sheets.clear();
  }
}

const styleSheetPool = new StyleSheetPool();

// Middleware to manage sheets per request
app.use((req, res, next) => {
  req.requestId = generateUniqueId();
  res.on('finish', () => {
    styleSheetPool.releaseSheet(req.requestId);
  });
  next();
});

SSR with Streaming

React 18 Concurrent Features

import { renderToPipeableStream } from 'react-dom/server';
import { ServerStyleSheet } from 'styled-components';

app.get('*', (req, res) => {
  const sheet = new ServerStyleSheet();
  
  let didError = false;
  
  const stream = renderToPipeableStream(
    sheet.collectStyles(<App />),
    {
      bootstrapScripts: ['/bundle.js'],
      onShellReady() {
        res.statusCode = didError ? 500 : 200;
        res.setHeader('Content-type', 'text/html');
        
        // Inject initial HTML with style placeholders
        res.write(`
          <!DOCTYPE html>
          <html>
            <head>
              <title>My App</title>
              <style id="styled-components-ssr"></style>
            </head>
            <body>
              <div id="root">
        `);
        
        stream.pipe(res, { end: false });
      },
      onAllReady() {
        // Inject collected styles
        const styles = sheet.getStyleTags();
        res.write(`
              </div>
              <script>
                document.getElementById('styled-components-ssr').outerHTML = \`${styles}\`;
              </script>
            </body>
          </html>
        `);
        res.end();
        sheet.seal();
      },
      onError(err) {
        didError = true;
        console.error('SSR Error:', err);
      },
    }
  );
  
  setTimeout(() => {
    if (!res.headersSent) {
      stream.abort();
      sheet.seal();
    }
  }, 10000); // 10 second timeout
});

Client-Side Style Rehydration

Preventing FOUC (Flash of Unstyled Content)

// client/index.js
import { hydrateRoot } from 'react-dom/client';

function initializeApp() {
  const container = document.getElementById('root');
  
  // Ensure styles are loaded before hydration
  const styleTags = document.querySelectorAll('style[data-styled]');
  
  if (styleTags.length > 0) {
    // Server-side styles are present, safe to hydrate
    hydrateRoot(container, <App />);
  } else {
    // No server styles, render client-only
    const root = createRoot(container);
    root.render(<App />);
  }
}

// Check if document is ready
if (document.readyState !== 'loading') {
  initializeApp();
} else {
  document.addEventListener('DOMContentLoaded', initializeApp);
}

Style Loading Detection

// Wait for critical styles to load
function waitForStyles(): Promise<void> {
  return new Promise((resolve) => {
    const checkStyles = () => {
      const styledTags = document.querySelectorAll('style[data-styled]');
      const linkTags = document.querySelectorAll('link[rel="stylesheet"]');
      
      let allLoaded = true;
      
      linkTags.forEach(link => {
        if (!link.sheet || link.sheet.cssRules.length === 0) {
          allLoaded = false;
        }
      });
      
      if (allLoaded && styledTags.length > 0) {
        resolve();
      } else {
        requestAnimationFrame(checkStyles);
      }
    };
    
    checkStyles();
  });
}

// Usage
async function hydrateWithStyles() {
  await waitForStyles();
  hydrateRoot(document.getElementById('root'), <App />);
}

Advanced SSR Patterns

Component-Level Style Extraction

// Extract styles for specific components
function extractComponentStyles(Component, props = {}) {
  const sheet = new ServerStyleSheet();
  
  try {
    renderToString(sheet.collectStyles(<Component {...props} />));
    return sheet.getStyleTags();
  } finally {
    sheet.seal();
  }
}

// Pre-render critical components
const criticalStyles = [
  extractComponentStyles(Header),
  extractComponentStyles(Navigation),
  extractComponentStyles(Footer),
].join('');

Conditional SSR Styling

function ConditionalSSRApp({ isBot }) {
  if (isBot) {
    // Full SSR for bots/crawlers
    return (
      <StyleSheetManager disableCSSOMInjection>
        <App />
      </StyleSheetManager>
    );
  }
  
  // Client-side rendering for users
  return <App />;
}

// Server route
app.get('*', (req, res) => {
  const isBot = /bot|crawler|spider/i.test(req.get('User-Agent'));
  
  if (isBot) {
    // Full SSR
    const { html, styles } = extractStyles(<ConditionalSSRApp isBot={true} />);
    res.send(createHTML(html, styles));
  } else {
    // SPA shell
    res.send(createSPAShell());
  }
});

Advanced SSR Contexts and Hooks

StyleSheet Context Management

Internal contexts and hooks for advanced styled-components integration and custom SSR scenarios.

// StyleSheet context interface
interface IStyleSheetContext {
  shouldForwardProp?: ShouldForwardProp<'web'> | undefined;
  styleSheet: StyleSheet;
  stylis: Stringifier;
}

// StyleSheet context and consumer
declare const StyleSheetContext: React.Context<IStyleSheetContext>;
declare const StyleSheetConsumer: React.Consumer<IStyleSheetContext>;

// Hook for accessing StyleSheet context
function useStyleSheetContext(): IStyleSheetContext;

// Stylis context for CSS processing
type IStylisContext = Stringifier | void;
declare const StylisContext: React.Context<IStylisContext>;
declare const StylisConsumer: React.Consumer<IStylisContext>;

Usage Examples:

import { useStyleSheetContext, StyleSheetContext } from 'styled-components';

// Custom hook for accessing stylesheet information
function useStyledContext() {
  const context = useStyleSheetContext();
  
  return {
    styleSheet: context.styleSheet,
    shouldForwardProp: context.shouldForwardProp,
    stylis: context.stylis,
  };
}

// Advanced SSR component with context access
function AdvancedSSRComponent() {
  const { styleSheet, stylis } = useStyleSheetContext();
  
  // Access to the underlying StyleSheet instance
  const registeredStyles = styleSheet.getIds();
  
  return (
    <div data-styled-ids={registeredStyles.join(',')}>
      Component content
    </div>
  );
}

// Custom StyleSheetManager wrapper
function CustomStyleSheetProvider({ children, customStylis }) {
  return (
    <StyleSheetContext.Provider value={{
      styleSheet: new StyleSheet(),
      stylis: customStylis,
      shouldForwardProp: undefined,
    }}>
      {children}
    </StyleSheetContext.Provider>
  );
}