Hi, I'm experiencing some problems when deploying using the useServerlessTraceTarget flag in my serverless.yml.
"@material-ui/core": "^4.11.0",
"next": "^9.4.4",
"serverless": "^1.73.1",
"serverless-next.js": "1.15.0-alpha.0",
When running locally, material-ui classNames - for example "makeStyles-test-1", are correctly represented in the rendered style elements as head->style-> .makeStyles-test-1 but when deployed with useServerlessTraceTarget:true elements have "makeStyles-test-1" but the head style uses something like .jss1. Conversely, when deployed without useServerlessTraceTarget set both styles are correctly called .jss1
Any help greatly appreciated.
Thanks
Have you already followed https://material-ui.com/styles/advanced/#next-js ?
This section covers more advanced usage of @material-ui/core/styles.
Hi, thanks for the reply.
Yes I have followed the documentation as per the link provided.
I can deploy without using the useServerlessTraceTarget flag and classnames in style tag and in components/elements are in sync. But deploying with useServerlessTraceTarget : true they are not.
I'm testing this against a very simple codebase with _app.js/_document.js & index.js pages and no next.config.js.
SSR of style sheets is working correctly but once the #jss-server-side style element is removed I loose all styles (when deployed with useServerlessTraceTarget).
To repeat - styles/classnames work correctly (i.e .jss_123) when deployed without useServerlessTraceTarget flag set in the serverless.yml
Thanks
I'm also using MaterialUI with this target and found the same issue - class names are mismatched.
For example, I have a search bar with class makeStyles-searchBar-14, but the CSS uses jss14. But after performing a client-side navigation, e.g going to another page the style gets fixed.
I guess it has something to do with that _document.js file used for SSR (in https://material-ui.com/styles/advanced/#next-js): https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_document.js. And it seems like it is built properly into the *.js files in the .next/serverless/pages directory for me, so not sure what the issue is.
EDIT: the _document.js file seems fine as far as I can tell. Though I noticed only elements that are in my layout (rendered in my _app.tsx file, shared with all pages) did not have the class names transformed to jss-*, but specific page elements did have class names changed to jss-*. Still investigating more.
This section covers more advanced usage of @material-ui/core/styles.
GitHub
React components for faster and easier web development. Build your own design system, or start with Material Design. - mui-org/material-ui
I think I figured out the problem and a solution, thanks to a semi-related issue in MaterialUI: https://github.com/mui-org/material-ui/issues/17423.
Turns out I had to set NODE_ENV=production in my .env file so when Next builds (e.g via Serverless), it can set this as an env variable to use in the compiled code. You'd have to set it here since Lambda@Edge doesn't support environment variables. Then MaterialUI uses it to determine whether to use the productionPrefix (by default it's jss) when generating class names.
Here is the relevant code in MaterialUI:
The Problem
Without the above, and with useServerlessTraceTarget turned on, and assuming you setup MaterialUi with next correctly (doc and app file setup), the classNames are generated with jss prefix on client side but in the actual server-response, it's using the development class name (e.g makeStyles-xxx-n). If i inspected the initial server response, the styles are all rendered correctly (all development class names).
But after the HTML is loaded in the browser, the client will remove all the injected JSS styles (if you followed MaterialUI's integration guide with Next, that's what this code does: https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_app.js#L13-L16) and then reinject client JSS styles with the jss prefix. The original styles are gone which then results in many components being unstyled, since they still use dev class names.
This fix of setting NODE_ENV=production should make it so that the server response uses jss prefix for class names as well.
Without useServerlessTraceTarget and without NODE_ENV=production, I did see that both client and server class names had jss prefix. So I think this is actually a gotcha in Next, since this new target seems to have different default behavior for server code, for some reasons NODE_ENV is not production for server page JS. It doesn't seem an issue with serverless-next.js. I think I got the same behavior when I ran the next build and next start without setting NODE_ENV=production and having a target of target: "experimental-serverless-trace" in my next.config.js
PS: If you want your own custom class name prefix that's not jss, you can follow https://github.com/mui-org/material-ui/issues/17423 to generate the same one on both client and server side. It involves editing both _app and _document (server-side) files with a custom generateClassNames function.
@develop-stuartohare hope this helps!
GitHub
React components for faster and easier web development. Build your own design system, or start with Material Design. - mui-org/material-ui
GitHub
React components for faster and easier web development. Build your own design system, or start with Material Design. - mui-org/material-ui
Closing this issue as the solution above should work, feel free to comment if the steps above didn't work :)
I can't seem to get this to work no matter how I add the env variable NODE_ENV=production.
On every page that uses getServerSideProps or getInitialProps I get makeStyles instead of jss. Could you upload a copy of your implementation, @dphang ?
Sure @paleite, here's a snippet (sorry I have a mixed class and functional component, but hopefully you get the idea):
_app.tsx (simplified snippet of mine)
export default function App(
props: AppProps
): JSX.Element | null {
const { Component, pageProps } = props;
const router = useRouter();
useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles && jssStyles.parentElement !== null) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
// Fix up class names -> here "a" will be the class name prefix for MUI instead of "jss". Match it in _document file
const generateClassName = createGenerateClassName({
productionPrefix: "a",
});
// Return the page
return (
<StylesProvider generateClassName={generateClassName}>
<UserContextProvider>
<ThemeProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</ThemeProvider>
</UserContextProvider>
</StylesProvider>
);
}
_document.tsx
import React from "react";
import Document, { Head, Main, NextScript } from "next/document";
import {
ServerStyleSheets,
createGenerateClassName,
} from "@material-ui/core/styles";
export default class MyDocument extends Document {
render(): JSX.Element {
return (
<html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// 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
// Render app and page and get the context of the page with collected side effects.
const generateClassName = createGenerateClassName({
productionPrefix: "a",
});
const sheets = new ServerStyleSheets({
serverGenerateClassName: generateClassName,
});
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
Check to make sure the page bundle has inlined the NODE_ENV=production environment variable. This was what is causing style name mismatch for me. For me I am using .env file which Next.js will inline as part of its build. I believe you can search for something similar:
processEnv([{"path":".env","contents":"NODE_ENV=production\n"}])
Thanks, @dphang . This really helped me pin my problem down and I got it working eventually.
It was completely unrelated (a component didn't support SSR), but having your example allowed me to find the other issue more easily.
Most helpful comment
Sure @paleite, here's a snippet (sorry I have a mixed class and functional component, but hopefully you get the idea):
_app.tsx (simplified snippet of mine)
_document.tsx
Check to make sure the page bundle has inlined the
NODE_ENV=productionenvironment variable. This was what is causing style name mismatch for me. For me I am using.envfile which Next.js will inline as part of its build. I believe you can search for something similar: