Skip to content

Server side rendering and browserless testing

Esteban Munoz Facusse edited this page Jul 11, 2022 · 19 revisions

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
yarn add @fluentui/react
  1. Create a _document.js file under your pages folder with the following content:
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:
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:

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.

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:

  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:

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>
  `
);

What's new

Planning

Process

Usage

Reporting issues

Contributing

Component creation and convergence

Testing

Coding guidelines

Best practices

References

Useful tools

Clone this wiki locally