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.
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;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'));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>
`;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);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'; // ❌ WrongEnvironment 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'));
}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);
};interface ServerRenderResult {
/** The rendered HTML string */
html: string;
/** The CSS rules generated during rendering */
css: string;
}