or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdfont-animation.mdindex.mdserver-rendering.mdstyle-sets.mdstyling.md
tile.json

server-rendering.mddocs/

Server-Side Rendering

Utilities for rendering styles on the server and extracting CSS rules for client hydration. This enables merge-styles to work seamlessly in server-side rendered applications by capturing generated CSS rules during render.

Important: Server-side rendering utilities are available from a separate import path: @uifabric/merge-styles/lib/server, not from the main package export.

Capabilities

renderStatic Function

Renders content and extracts CSS rules generated during the render process. Essential for server-side rendering where you need to capture dynamically generated styles.

/**
 * Renders content and returns both HTML and CSS needed for the HTML
 * @param onRender - Function that returns the HTML string to render
 * @param namespace - Optional namespace to prepend to CSS class names to avoid collisions
 * @returns Object containing html string and css string
 */
function renderStatic(onRender: () => string, namespace?: string): { html: string; css: string };

Usage Examples:

React Server-Side Rendering:

import { renderStatic } from '@uifabric/merge-styles/lib/server';
import { renderToString } from 'react-dom/server';
import { MyApp } from './MyApp';

// Server-side render with style extraction
const { html, css } = renderStatic(() => {
  return renderToString(<MyApp />);
});

// Send HTML with extracted CSS
const fullHtml = `
<!DOCTYPE html>
<html>
  <head>
    <style>${css}</style>
  </head>
  <body>
    <div id="root">${html}</div>
  </body>
</html>
`;

With Namespace for Multi-App Scenarios:

// Prevent CSS class name collisions
const { html, css } = renderStatic(() => {
  return renderToString(<MyApp />);
}, 'myapp'); // Classes will be prefixed with 'myapp-'

// CSS classes become: .myapp-css-0, .myapp-root-1, etc.

Next.js Integration:

// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { renderStatic } from '@uifabric/merge-styles/lib/server';

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const originalRenderPage = ctx.renderPage;

    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App) => (props) => {
          const { html, css } = renderStatic(() => <App {...props} />);
          return { html, css };
        },
      });

    const initialProps = await Document.getInitialProps(ctx);
    return initialProps;
  }

  render() {
    return (
      <Html>
        <Head>
          <style dangerouslySetInnerHTML={{ __html: this.props.css }} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

Server-Side Rendering Considerations

Rule Registration Timing:

// ❌ Problematic: Rules registered at file scope won't be captured
const rootClass = mergeStyles({ background: 'red' });

const App = () => <div className={rootClass} />;

// This won't capture the rootClass styles
renderStatic(() => ReactDOM.renderToString(<App />));

// ✅ Better: Register styles during render
const App = () => {
  const rootClass = mergeStyles({ background: 'red' });
  return <div className={rootClass} />;
};

// This will capture all styles
renderStatic(() => ReactDOM.renderToString(<App />));

Performance Optimization with Memoization:

import { memoizeFunction } from '@uifabric/utilities';

// Memoize style functions to reduce recalculation overhead
const getStyles = memoizeFunction((theme: ITheme) => {
  return mergeStyleSets({
    root: { 
      background: theme.palette.neutralLight,
      padding: '20px'
    },
    title: {
      color: theme.palette.neutralPrimary,
      fontSize: theme.fonts.large.fontSize
    }
  });
});

const MyComponent = ({ theme }) => {
  const styles = getStyles(theme);
  return (
    <div className={styles.root}>
      <h1 className={styles.title}>Title</h1>
    </div>
  );
};

Client-Side Hydration:

// Client-side hydration setup
import { hydrate } from 'react-dom';
import { MyApp } from './MyApp';

// The client will reuse server-generated CSS classes
// and generate new ones as needed
hydrate(<MyApp />, document.getElementById('root'));

Namespace Collision Prevention

When running multiple applications or versions on the same page, use namespaces to prevent CSS class name collisions.

// App 1
const { html: html1, css: css1 } = renderStatic(() => {
  return renderToString(<App1 />);
}, 'app1');

// App 2  
const { html: html2, css: css2 } = renderStatic(() => {
  return renderToString(<App2 />);
}, 'app2');

// Combined HTML with isolated CSS
const combinedHtml = `
<style>${css1}</style>
<style>${css2}</style>
<div id="app1">${html1}</div>
<div id="app2">${html2}</div>
`;

Configuration for Server-Side Rendering

The renderStatic function automatically configures the Stylesheet for server-side use, but you can also manually configure it.

import { Stylesheet, InjectionMode } from '@uifabric/merge-styles';

// Manual server-side configuration
const stylesheet = Stylesheet.getInstance();
stylesheet.setConfig({
  injectionMode: InjectionMode.none, // Don't inject into DOM
  namespace: 'myapp' // Add namespace prefix
});

// Reset before each render
stylesheet.reset();

// Render your app
const html = renderToString(<MyApp />);

// Get generated CSS
const css = stylesheet.getRules(true);

Server Environment Setup

Import Path:

// Server-side specific import
import { renderStatic } from '@uifabric/merge-styles/lib/server';

// Don't import from main entry point on server
// import { renderStatic } from '@uifabric/merge-styles'; // ❌ Wrong

Environment Detection:

// The library automatically detects server environment
// You can also check manually:
const isServer = typeof document === 'undefined';

if (isServer) {
  // Server-side logic
  const { html, css } = renderStatic(() => renderToString(<App />));
} else {
  // Client-side logic
  render(<App />, document.getElementById('root'));
}

Common Patterns

Express.js Integration:

import express from 'express';
import { renderToString } from 'react-dom/server';
import { renderStatic } from '@uifabric/merge-styles/lib/server';

const app = express();

app.get('*', (req, res) => {
  const { html, css } = renderStatic(() => {
    return renderToString(<App url={req.url} />);
  });

  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <style>${css}</style>
      </head>
      <body>
        <div id="root">${html}</div>
        <script src="/client.js"></script>
      </body>
    </html>
  `);
});

Gatsby Plugin Integration:

// gatsby-ssr.js
import { renderStatic } from '@uifabric/merge-styles/lib/server';

export const replaceRenderer = ({ bodyComponent, replaceBodyHTMLString, setHeadComponents }) => {
  const { html, css } = renderStatic(() => bodyComponent);

  // Inject CSS into head
  setHeadComponents([
    <style key="merge-styles" dangerouslySetInnerHTML={{ __html: css }} />
  ]);

  // Use extracted HTML
  replaceBodyHTMLString(html);
};

Return Types

interface ServerRenderResult {
  /** The rendered HTML string */
  html: string;
  /** The CSS rules generated during rendering */
  css: string;
}