Fluentui: Refine and update server side rendering support

Created on 26 Nov 2016  路  39Comments  路  Source: microsoft/fluentui

I'm checking out Fabric in a project of mine. My project uses server side rendering. I've just spent the last hour or so plugging together some code and encountered the following error. This makes me thing that server side rendering is not yet supported. Is this something planned? I can't abandon server rendering, and it's looking like I may have to abandon Fabric. :(

ReferenceError: document is not defined
   at registerStyles (C:\Users\Ryan\Projects\league\node_modules\@microsoft\load-themed-styles\lib\index.js:167:16)
   at applyThemableStyles (C:\Users\Ryan\Projects\league\node_modules\@microsoft\load-themed-styles\lib\index.js:60:13)
   at Object.loadStyles (C:\Users\Ryan\Projects\league\node_modules\@microsoft\load-themed-styles\lib\index.js:35:5)
   at Object.<anonymous> (C:\Users\Ryan\Projects\league\node_modules\office-ui-fabric-react\lib\components\Label\Label.scss.js:4:22)
   at Module._compile (module.js:573:32)
   at Object.Module._extensions..js (module.js:582:10)
   at Module.load (module.js:490:32)
   at tryModuleLoad (module.js:449:12)
   at Function.Module._load (module.js:441:3)
   at Module.require (module.js:500:17)
Documentation Author Feedback Backlog review Feature

Most helpful comment

@dzearing can you give me an update about the updated SSR approach yet?

All 39 comments

There is a workaround here. On the server:

let { configureLoadStyles } = require('@microsoft/load-themed-styles');

configureLoadStyles((styles) => { /* Inject given styles string onto server rendered page */ });

Can you try that?

Missing docs here. We'll need to include a blurb about SSR setups.

I'm not explicitly doing anything with the styles, yet. I'm just simply trying to render a TextField with whatever the defaults are.

Would I need to load these styles, and then somehow use them, everywhere that I use a Fabric component? Or is this something I do, once, prior to rendering on the server?

Edit: Would a single snippet like below, on the server before rendering, suffice?

import { loadTheme } from '@ms/office-ui-fabric-react';

loadTheme({
  'themePrimary': 'red'
});

Say that you load 5 Button components and a TextField. This function will be called 2 times, for each unique component css that needs to be loaded.

You just need to take the strings and inject it into a <style> element on your page. That way the components will show up styled correctly and you won't be looking at a broken page while waiting for some CSS to load.

Hmm, OK I think I'm understanding. Before I proceed with what I think I'm understanding (lol), would you mind clarifying something that I just came across. I came across this test file that looks related to SSR. It uses a function that I'm not using, yet.

library.setSSR(true);

Is this required?

Nope. It's specifically for testing ssr cases. In some circumstances we need to tell our helpers that window and document are undefined to simulate an ssr environment. We should probably have named it __setSSRTestingMode to be way more clear! :)

No worries, thanks for explaining.

So based on your prior answer about the styles, and skimming the code, it sounds like Fabric is not using inline styles but is attempting to inject styles into a style element at run time? This means my current configuration is failing while rendering on server because the code trying to reference document is failing with the above message.

So, the fix is to provide a style sheet, essentially, by preemptively creating all the needed style elements, once, on the server? This would be done using the provided example code of yours.

I'm just trying to make sure I understand fully. I've got a couple of new questions now ...

  • If the provided code of yours is the fix, and manually building up a style element with all styles of used components is done on the server, from that point forward components will see that the styles exist and will not attempt to inject on its own, thus avoiding the document usage?
  • Given the above, could we create a static style sheet for this, and tell Fabric not to try to inject it at run time without having to write the code to build out the style element manually?

Thanks!

Contextual Note: I'm currently just using the CDN version of the Fabric Core CSS style sheet, in conjunction with Fabric React installed via NPM and imported using import {TextField} from ".../lib/TextField".

Correct, for the components we're not using inline styles. Instead we convert our sass output into commonjs modules that inject style elements at runtime efficiently on demand.

Answers to questions:

If you provide a function callback that burns on the styles, the components you render on server will be styled correctly.

I don't think you will want to create a static stylesheet. It defeats the purpose of server side rendering which is to get something rendered on the screen without waiting for the resources to start processing. But maybe there's a good reason I'm not seeing.

When you bundle the TextField into a JS bundle, it will bundle the css with it, which unfortunately will be redundantly downloaded in this case (duplicate of the css you burn on the page) but shouldn't cause issues. I'm not sure how to avoid that aside from totally moving to inline styles which introduces other limitations.

Got it. I think here's my last question now that I'm understanding how this works ...

In your initial explanation regarding multiple Button and a TextField, that sounds like a pretty simple example where you have all of those within the same React component and know that you only need those 2 Fiber components' styles there, right? What about a real world scenario where I will have many React components across many files, all using Fiber Button, TextField, etc - where to perform this initial manual style injection efficiently?

Based on my understanding, I'm expecting you to say that each React component that uses a Fiber component(s) needs to perform the above code (of yours) for its own Fiber components? In the end, I could still end up with many style elements that are redundant due to the Fiber components being used in other React components and having their style elements injected elsewhere?

I guess I could also have some a "fiber-ssr-style-hack.js" file that I run on a SSR render that injects all possible styles of fiber components that I know I've used throughout the app. This would completely undo your efforts on that CSS optimizations, but it would simplify SSR for the time being.

Thoughts?

Edit: sorry, I kept typing Fiber instead of Fabric.

Alright, well, after messing around with your suggestion I've got the application rendering on the server. I literally don't think it's doing anything, but it appears to be tricking the Fabric code, on the server, into not touching document, which allows the server rendering to proceed.

I'm not creating any style elements or anything. I'm simply printing the styles out, to stdout, using console.log before I use my server side rendering logic.

I'm very confused as to how the Fabric components know that they don't have to inject their styles as a result of me calling this function. I literally just call the function and print the styles. If I remove the console.log of the styles the Fabric components attempt to inject again.

I think I understand what you're hitting. I haven't touched SSR on my end, but I have colleagues that are dealing with this right now and will get their take on how they are approaching this.

If I'm understanding correctly, what may be getting in the way here is that the styles are only injected on initial load when the module graph is parsed. That means on subsequent server renders, where the components are already loaded, the loadStyles function wouldn't be called again, which would be a huge problem because you wouldn't have the exact css required for the page rendering.

If this is the case I will see what we can do on our end to iron this out and make it easier.

Is there any solution in SSR? I'm testing with next.js but i need SSR.

Office UI Fabrice doesn't support server side rendering yet?

@ENDiGo @mantap123

I've updated the README.md to include a blurb on how to do SSR. Hope this helps.

@dzearing - I've read the workaround but am having a hard time figuring out how to apply it to an AspNetCore 2.0 app. Since the header is rendered in the _Layout.cshtml file I'm not quite sure where to put the suggested code. Any suggestions/help would be much appreciated.

@SimplerSoftware

  1. Create a file named fabric-style-server.ts
// Workaround for Office Fabric SSR
import { configureLoadStyles } from '@microsoft/load-themed-styles';

// Store registered styles in a variable used later for injection.
let allStyles: string[] = [];

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

export const styles = allStyles;
  1. Import it in boot-server.tsx
import * as React from 'react';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { replace } from 'react-router-redux';
import { createMemoryHistory } from 'history';
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
import { styles } from './fabric-style-server';
// import it before routes and configureStore, very important.
import { routes } from './routes';
import configureStore from './configureStore';

// ...
  1. Add fabric styles to SSR Result
// The end of boot-server.tsx
params.domainTasks.then(() => {
            let result = renderToString(app);
            let styleTag = `<style>${styles.join('')}</style>`;
            resolve({
                html: styleTag + result,
                globals: { initialReduxState: store.getState() }
            });
        }, reject);
  1. Do not place fabric in vendor list, there no tree shaking in React Template, import its component via path

Now, enjoy fabric in your asp.net core 2.0 project~


But, there is still one problems: Not all registered styles are written into html page. For example, I have a component like this:

const MyPage = () => (
  <Fabric>
    <DefaultButton>
      I am a button.
    </DefaultButton>
  </Fabric>
);

There are only 31 rules in SSR result when I disabled javascript in browser, they are:

.scrollDisabled_2d80166b{overflow:hidden !important}
.root_707e196d{overflow:hidden}.rootIsMaximizeFrame_707e196d{height:100%;width:100%}.image_707e196d{display:block;opacity:0}.image_707e196d.imageIsLoaded_707e196d{opacity:1}.imageIsCenter_707e196d,.imageIsContain_707e196d,.imageIsCover_707e196d{position:relative;top:50%}[dir='ltr'] .imageIsCenter_707e196d,[dir='ltr'] .imageIsContain_707e196d,[dir='ltr'] .imageIsCover_707e196d{left:50%}[dir='rtl'] .imageIsCenter_707e196d,[dir='rtl'] .imageIsContain_707e196d,[dir='rtl'] .imageIsCover_707e196d{right:50%}html[dir='ltr'] .imageIsCenter_707e196d,html[dir='ltr'] .imageIsContain_707e196d,html[dir='ltr'] .imageIsCover_707e196d{-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%)}html[dir='rtl'] .imageIsCenter_707e196d,html[dir='rtl'] .imageIsContain_707e196d,html[dir='rtl'] .imageIsCover_707e196d{-webkit-transform:translate(50%, -50%);transform:translate(50%, -50%)}.imageIsContain_707e196d.imageIsLandscape_707e196d{width:100%;height:auto}.imageIsContain_707e196d.imageIsPortrait_707e196d{height:100%;width:auto}.imageIsCover_707e196d.imageIsLandscape_707e196d{height:100%;width:auto}.imageIsCover_707e196d.imageIsPortrait_707e196d{width:100%;height:auto}.imageIsNone_707e196d{height:auto;width:auto}.imageIsScaleWidthHeight_707e196d{height:100%;width:100%}.imageIsScaleWidth_707e196d{height:auto;width:100%}.imageIsScaleHeight_707e196d{height:100%;width:auto}
.root_c726b21d{position:absolute;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #eaeaea}[dir='ltr'] .root_c726b21d{-webkit-box-shadow:0 0 5px 0px rgba(0,0,0,0.4);box-shadow:0 0 5px 0px rgba(0,0,0,0.4)}[dir='rtl'] .root_c726b21d{-webkit-box-shadow:0 0 5px 0px rgba(0,0,0,0.4);box-shadow:0 0 5px 0px rgba(0,0,0,0.4)}.root_c726b21d::-moz-focus-inner{border:0}.root_c726b21d{outline:transparent}@media screen and (-ms-high-contrast: active){.root_c726b21d{border:1px solid WindowText}}.container_c726b21d{position:relative}.main_c726b21d{background-color:#ffffff;overflow-x:hidden;overflow-y:auto;position:relative}.overFlowYHidden_c726b21d{overflow-y:hidden}.beak_c726b21d{position:absolute;background-color:#ffffff;-webkit-box-shadow:inherit;box-shadow:inherit;border:inherit;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.beakCurtain_c726b21d{position:absolute;top:0;right:0;bottom:0;left:0;background-color:#ffffff}
.rootIsFixed_a23137d4{position:fixed;z-index:1000000;top:0;left:0;width:100vw;height:100vh;visibility:hidden}.content_a23137d4{visibility:visible}

Thanks!

Yes, thank you, @ZeekoZhu

@limefrogyank did this work for you?

It's as he said in his extra comment...not all of the styles are getting sent. My buttons are still showing as plain for a half second until the client takes over the styling.

Also, I'm running into one of the other errors where the server side style doesn't match the client style so it's reloading everything.

I am re-opening this! We have an updated SSR approach that is far better than the old one. We are just about complete with supporting it.

Assigning this to myself, will get this working.

@dzearing can you give me an update about the updated SSR approach yet?

any update about this item?

@dzearing Would love to use UI Fabric with Next.js but somehow can't make it work without this feature. Is there any ETA of this updated SSR support?

Any updates on the updated SSR support? Getting errors with generated ids when using with Next.js:

Warning: Prop `id` did not match. Server: "id__3" Client: "id__0"

Here is a simple app that shows the issue: https://github.com/juliomenendez/nextjs-example-with-office-ui-fabric-react

@juliomenendez I had the same problem. After a little investigation, I found the function that causes the issue:

https://github.com/OfficeDev/office-ui-fabric-react/blob/master/packages/utilities/src/object.ts#L94

It uses a global incrementing counter (window on client or process on server). To reset, add the following to somewhere that runs before every render:

process.__currentId__ = 0

@jleck awesome! That worked, thank you!

@juliomenendez Hi Julio, I checked out your repository but I still can notice some flickering of the component (button) when page loads (due the style loading). Am I doing something wrong or there are some more work need to do on this? Thanks.

@levybeedev You need to configure the styles for SSR:

https://github.com/OfficeDev/office-ui-fabric-react/wiki/Server-side-rendering-and-browserless-testing

Hope that helps! :)

@jleck thanks for that! I'm going to update my Next.js example to reflect it. @levybeedev try that, hopefully it helps.

The SSR example doesn't seem to solve the issue. I haven't been able to explore configureLoadStyles yet but I'm assuming the issue is in there. I don't think the callback parameter is receiving any arguments.

Unless there's an implementation issue? Here's a CodeSandbox example with NextJS: https://codesandbox.io/s/xvkrn5wx2p

Try the full screen preview upon refresh to demonstrate the delay while merge-styles injects the styles: https://xvkrn5wx2p.sse.codesandbox.io/

It's unfortunate that merge-styles requires client JS to load styles in SSR.

@brettinternet I haven't looked into it yet, but I couldn't get the callback to fire at all last time I checked. That was on my list to check.

Any news on SSR support? We're using Fabric with Next and running in the same issues as described above.

Any news on SSR support? We're using Fabric with Next and running in the same issues as described above.

This issue make me headaches as well. For temporary solution make initial template and load them with dynamic import in _app.(tsx|jsx).

// components/template.tsx
export default ({children}) => <Fabric>{children}</Fabric>
// pages/_app.tsx
import React from 'react';
import NextApp from 'next/app';
import dynamic from 'next/dynamic';

const Template = dynamic(import('../components/template'), { ssr: false });

export class App extends NextApp {
  public render() {
    const { Component, pageProps } = this.props;
    return (
      <Template>
        <Component {...pageProps} />
      </Template>
    );
  }
}

export default App;

I'm also having this issue. configureLoadStyles is never called on my tests so the documentation does not work for me. Is it outdated?
I'm using next.js.

@dzearing Would the team be able to speak to SSR support? What might be in the future for merge-styles?

The wiki for SSR and browserless testing was recently updated (this past December). Hopefully this helps!

This issue has been automatically marked as stale because it has marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. Thank you for your contributions to Fabric React!

In my case this workaround wasn't enough, because some pages were rerendered (first with plain styles, then with the actual styles applied). I had to use memoizeFunction for mergeStyleSets and know it works like a charm. You can find some info on this function here.

Was this page helpful?
0 / 5 - 0 ratings