React-JSS with material-ui and server side rendering

Viewed 1345

I have a react application and a custom component library built using material-ui. Components within the react application itself uses react-jss for styling and I'd like to avoid too many references to material-ui in the app itself just in case I wish to replace material-ui in the future.

From what I've read it seems that Material-UI uses a wrapped version of JSS so they should be compatible, but I'm unable to extract both my own styles as well as those from material-ui. I seem to be able to extract one or the other though...

I've followed the React-JSS Server Side Rendering guide and that works fine, but I'm wondering how I can most easily combine styles from both without having to resort to using @material-ui/core/styles everywhere.

Here is my bootstrap component that registers the JssProvider in my app:

export const Bootstrap: React.FC<IBootstrapProps> = ({
    sheetsRegistry,
    generateId
}) => {
    return (
        <JssProvider registry={sheetsRegistry} generateId={generateId}>
            <App />
        </JssProvider>
    )
}

export default Bootstrap

The sheetsRegistry and generateId props comes from a render function called directly from an express handler:

const render = (props: ISSRRenderProps) => {
    const sheetsRegistry = new SheetsRegistry()
    const generateId = createGenerateId()

    const appContent = renderToString(
        <Bootstrap
            sheetsRegistry={sheetsRegistry}
            generateId={generateId}
        />
    )
    return {
        appContent,
        styles: sheetsRegistry.toString()
    }
}

This gives me all my custom styles in the returned styles property which I in turn dump to the page without issues. The problem comes when I try to combine this with @material-ui. Simply including a component from the library does not give me it's styles server side though they do render on the client after a FOUC.

Using the server rendering guide from material-ui and modifying my Bootstrap component does give my the material-ui styles, but now my own styles are not collected. This also adds a single reference to material-ui in my react app, but if that is what it takes I can live with it as long as sub-components don't have to use material-uis style tools.

import { ServerStyleSheets } from "@material-ui/core/styles"

export const Bootstrap: React.FC<IBootstrapProps> = ({
    sheetsRegistry,
    generateId,
    ...rest
}) => {
    const sheets = new ServerStyleSheets()
    sheetsRegistry?.add(sheets as any)
    return sheets.collect(
        <JssProvider registry={sheetsRegistry} generateId={generateId}>
            <App {...rest} />
        </JssProvider>
    )
}

Is there a way to get the best of both worlds? I'd love to use Material-UI components in my UI library and extract all styles globally for server side rendering.

1 Answers

I've been able to come up with a "workaround" by modifying my render function to collect material-ui styles separately and just concatenating them:

import { ServerStyleSheets } from "@material-ui/core/styles"

const render = (props: ISSRRenderProps) => {
    const sheetsRegistry = new SheetsRegistry()
    const generateId = createGenerateId()
    const muiStyles = new ServerStyleSheets()

    const appContent = renderToString(
        muiStyles.collect(
            <Bootstrap
                sheetsRegistry={sheetsRegistry}
                generateId={generateId}
            />
        )
    )

    return {
        appContent,
        styles: muiStyles.toString() + sheetsRegistry.toString()
    }
}

The reason believe this is a workaround and may not be the best solution is that I still have to separately handle my own styles vs material-ui styles. Seeing as they both use JSS behind the scenes I still think it should be possible to seamlessly collect both in the same sheetsRegistry, but this works for now.

Related