Back to Fluentui

Server Side Rendering And Browserless Testing

docs/react-wiki-archive/Server-side-rendering-and-browserless-testing.md

4.40.2-hotfix25.7 KB
Original Source

Server-side rendering

Next.js setup

For basic instructions on getting Next.js set up, see https://nextjs.org/

  1. Get a basic next.js setup running, rendering a page from the pages folder, as guided by the tutorial.
  2. Add a dependency on @fluentui/react
bash
yarn add @fluentui/react
  1. Create a _document.js file under your pages folder with the following content:
tsx
import * as React from 'react';
import Document, { Head, Html, Main, NextScript } from 'next/document';
import { Stylesheet, resetIds } from '@fluentui/react';
// Fluent UI React (Fabric) 7 or earlier
// import { Stylesheet, resetIds } from 'office-ui-fabric-react';

const stylesheet = Stylesheet.getInstance();

// Now set up the document, and just reset the stylesheet.
export default class MyDocument extends Document {
  static getInitialProps({ renderPage }) {
    resetIds();

    const page = renderPage(App => props => <App {...props} />);

    return { ...page, styleTags: stylesheet.getRules(true), serializedStylesheet: stylesheet.serialize() };
  }

  render() {
    return (
      <Html>
        <Head>
          <style type="text/css" dangerouslySetInnerHTML={{ __html: this.props.styleTags }} />
          <!--
            This is one example on how to pass the data.
            The main purpose is to set the config before the Stylesheet gets initialised on the client.
            Use whatever method works best for your setup to achieve that.
          -->
          <script type="text/javascript" dangerouslySetInnerHTML={{ __html: `
            window.FabricConfig = window.FabricConfig || {};
            window.FabricConfig.serializedStylesheet = ${this.props.serializedStylesheet};
          ` }} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}
  1. You should now be able to server render Fluent UI React components in any of your pages:
js
import * as React from 'react';
import {
  Checkbox,
  ColorPicker,
  createTheme,
  Dropdown,
  ThemeProvider, // NOTE: Use Fabric instead in version 7 or earlier
  initializeIcons,
  PrimaryButton,
  Slider,
  TextField,
  Toggle,
} from '@fluentui/react';

initializeIcons();

const Index = () => (
  <ThemeProvider>
    <div>
      <PrimaryButton>Hello, world</PrimaryButton>
      <Toggle defaultChecked label="Hello" />
      <TextField defaultValue="hello" />
      <Dropdown disabled />
      <Checkbox defaultChecked label="Hello" />
      <Slider defaultValue={50} max={100} />
      <ColorPicker />
    </div>
  </ThemeProvider>
);
export default Index;

node.js setup

Note: There are many steps missing below to get nodemon/babel/typescript/es modules working in a node.js environment. This will need to be elaborated on.

It's possible to render Fluent UI React components on the server side in a Node environment, using the SSR support in merge-styles.

See https://codesandbox.io/s/dazzling-montalcini-kv9bz for an example which uses the SSR support to build html/css strings to inject into the page. The result is not mounted, so behaviors will not work, but represents the html/css output that would be generated by SSR.

Example:

tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom/server';

import {
  ThemeProvider, // NOTE: Use Fabric instead in version 7 or earlier
  // ...
} from '@fluentui/react';
import { renderStatic } from '@fluentui/merge-styles/lib/server';
// Fluent UI React (Fabric) 7 or earlier
// import { renderStatic } from '@uifabric/merge-styles/lib/server';

import './styles.css';

initializeIcons();

function App() {
  return <ThemeProvider>...content goes here...</ThemeProvider>;
}

const serverRenderExample = () => {
  const { html, css } = renderStatic(() => ReactDOM.renderToString(<App />));

  // Use the html and css string content to inject into the response
};

serverRenderExample();

Browserless testing

In unit or end-to-end tests that run in an SSR-like (non-browser) environment such as Node, you'll need to disable style loading.

js
const { initializeIcons, setRTL, setResponsiveMode, ResponsiveMode } = require('@fluentui/react');
const themeLoader = require('@microsoft/load-themed-styles');

initializeIcons('dist/');

// Configure load-themed-styles to avoid registering styles.
themeLoader.configureLoadStyles(styles => {
  // noop
});

// Set rtl to false.
setRTL(false);

// Assume a large screen.
setResponsiveMode(ResponsiveMode.large);

You'll also want to mock out requiring .scss files. In Jest:

js
  moduleNameMapper: {
    // jest-style-mock.js should just contain module.exports = {};
    '\\.(scss)$': path.resolve(__dirname, 'jest-style-mock.js'),
  }

Legacy reference

Some of our legacy styling was done through scss rather than merge-styles. Keeping legacy info here in case there are still scenarios which need to pipe load-themed-styles based styling into a server response.

The basic idea is to tell the styles loader to store styles in a variable, which you can later inject into your page. Example:

tsx
import { configureLoadStyles } from '@microsoft/load-themed-styles';

// Store registered styles in a variable used later for injection.
let _allStyles = '';

// Push styles into variables for injecting later.
configureLoadStyles((styles: string) => {
  _allStyles += styles;
});

import * as React from 'react';
import * as ReactDOMServer from 'react-dom/server';
import { Text } from '@fluentui/react';

let body = ReactDOMServer.renderToString(<Text>hello</Text>);

console.log(
  `
  <html>
  <head>
    <style>${_allStyles}</style>
  </head>
  <body>
    ${body}
  </body>
  </html>
  `,
);