I first asked this question on StackOverflow, but got no reply. I believe this is important enough to be addressed here, as it is a stumbling block in the adoption of office-ui-fabric-react in the community.
I'm trying to get office-ui-fabric-react v7 to work in Next.js v9, and I'm having trouble getting the SSR set up.
Some previous links I've stumbled upon:
I haven't seen a working implementation with the latest versions of these libraries (v7 and v9, respectively). How to get these to work together?
Without SSR on page load it takes a second for the styles to be applied, which is not optimal.
Microsoft's official documentation mentions the use of this import:
import { renderStatic } from '@uifabric/merge-styles/lib/server';
But when importing that in _document.tsx I get this error:
[ error ] \node_modules\@uifabric\merge-styles\lib\server.js:1
import { InjectionMode, Stylesheet } from './Stylesheet';
^^^^^^
SyntaxError: Cannot use import statement outside a module
What's the solution?
Maybe this documentation can help clarify some things. Please let us know if it does or doesn't so we can think about next steps.
@dandrei Can you try this for getting Fabric to work with Next.js?
There is still a "className" mismatch warning, but hopefully the guide helps.
I'm still thinking about ways to get around the mismatch problem. It's not causing a problem as far as I can see, but it could be.
Thanks, @dzearing, the code you posted in this comment worked great.
I'll address 4 extra topics:
<style jsx> tags to be server-rendered.Had to add the following to also get ZEIT's styled-jsx to get added to the SSR'd code, in _document.tsx:
First, the import:
import flush from "styled-jsx/server";
Then, getInitialProps needs to return:
return {
...page,
styleTags: stylesheet.getRules(true),
styles: flush()
};
className mismatchI confirm that I also get the className mismatch.
Here's how material-ui deal with this, in their official nextjs example and their server rendering guide.
I wonder if I can somehow re-use their ServerStyleSheets class, even if this means installing @material-ui/styles, or if this would require a change in office-ui-fabric-react.
One remaining issue I have is the fact that the icons don't get rendered. They flicker on for a second after the page loads, then go away when the client-side code is fully loaded.
Console just says:
warn.js:13 The icon "chevrondown" was used but not registered.
To get icons to work in Next.js, I called initializeIcons(); in my _app.tsx file.
Calling it in _document.tsx caused the problem.
office-ui-fabric-react/lib/...Didn't need to do this with the new code you posted above. That's great, having to enable --experimental-modules would have been a pain with Next.js as the command to start it is just next, and it doesn't take / pass that parameter on to Node.js.
@dandrei The classname mismatch I almost have a fix. I believe I understand it more. It's related to keyframes registration, which happens on module load, rather than on render. I believe in altering this we could fix the issue. I'm hoping i can just get that working.
I ended up moving the initializeIcons call to the page itself. It needs to be called on the client side. I am not sure if the code in _app.tsx is only called on the server side (I'm a next.js noob...but I'm learning :)
Also THANK YOU for the validation and responses! Really appreciated!
I ended up moving the
initializeIconscall to the page itself. It needs to be called on the client side. I am not sure if the code in_app.tsxis only called on the server side.
Here's the Next.js resolution order (not sure why I can't find a link to it in the documentation, I found it as a comment in some code samples):
On the server:
1. app.getInitialProps
2. page.getInitialProps
3. document.getInitialProps
4. app.render
5. page.render
6. document.render
On the server with error:
1. document.getInitialProps
2. app.render
3. page.render
4. document.render
On the client
1. app.getInitialProps
2. page.getInitialProps
3. app.render
4. page.render
Anything in _app.tsx will run client side as if it were on a page. So including it once here will make it run for every client page.
Hi @dandrei, it's great that everything seems to be working for you now. Thank you for all your feedback, contribution and responses! I was wondering, is there any actionable items you think are still not done to address everything in this issue? Or do you think there's nothing else to be done? Thanks again for everything and please let us know if you need anything else!
Hi @khmakoto, thanks for the team's quick response. I have three remaining questions, the first having to do specifically with Next.js, and the next two are general ones:
See the this reply above.
Nav getting deprecated?As I'm trying to build an "admin dashboard" template using this library, I was surprised to read in this thread that the Nav component is getting deprecated.
Does that mean that new components will be added to office-ui-fabric-react to replace it?
I tried replicating the Outlook Mail web interface using only components from office-ui-fabric-react, and there were some structural elements I couldn't find, such as the top bar. I presume these have been built by hand in their respective applications.
It would be nice if devs who want to build entire interfaces in office-ui-fabric-react had access to some basic common templates to offer a basic starting point for the standard use cases: "landing page", "admin dashboard", etc.
Thanks.
I've followed the guide but I still don't get the theme styling from the server. stylesheet.getRules(true) returns @keyframes and @font-face rules but no more than that. The actual theme doesn't get applied until the client has loaded the JS. Is there no way around this?
Tested this today
I think the updated reference should be
import * as React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { Stylesheet, InjectionMode } from "@uifabric/merge-styles";
import { resetIds } from "@uifabric/utilities";
// Do this in file scope to initialize the stylesheet before Fabric components are imported.
const stylesheet = Stylesheet.getInstance();
// Set the config.
stylesheet.setConfig({
injectionMode: InjectionMode.none,
namespace: "server",
});
// Now set up the document, and just reset the stylesheet.
export default class MyDocument extends Document {
static getInitialProps({ renderPage }) {
stylesheet.reset();
resetIds();
const page = renderPage((App) => (props) => <App {...props} />);
return { ...page, styleTags: stylesheet.getRules(true) };
}
render() {
return (
<Html>
<Head>
<style
type="text/css"
dangerouslySetInnerHTML={{ __html: this.props.styleTags }}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}