Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[examples] Add example using nextjs & @mui/styles as a starter for the migration to v5 #33005

Merged
merged 7 commits into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ You can find some example projects in the [GitHub repository](https://github.com
- [Tailwind CSS](https://github.com/mui/material-ui/tree/master/examples/tailwind-css)
- [Vite.js](https://github.com/mui/material-ui/tree/master/examples/vitejs)
- [Use styled-components as style engine](https://github.com/mui/material-ui/tree/master/examples/create-react-app-with-styled-components) ([TypeScript version](https://github.com/mui/material-ui/tree/master/examples/create-react-app-with-styled-components-typescript))
- [Next.js + @mui/styles (v4 migration helper)](https://github.com/mui/material-ui/tree/master/examples/nextjs-with-typescript-v4-migration)

Create React App is an awesome project for learning React.
Have a look at [the alternatives available](https://github.com/facebook/create-react-app/blob/HEAD/README.md#popular-alternatives) to see which project best fits your needs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ One of the biggest changes in v5 is the replacement of JSS for [Emotion](https:/
Note that you may continue to use JSS for adding overrides for the components (e.g. `makeStyles`, `withStyles`) even after migrating to v5.
Then, if at any point you want to move over to the new styling engine, you can refactor your components progressively.

:::info
If you are using Next.js and you are not sure how to configure SSR to work with both Emotion & JSS, take a look a this [example project](https://github.com/mui/material-ui/tree/master/examples/nextjs-with-typescript-v4-migration).
:::

This document reviews all the steps necessary to migrate away from JSS.

While you can use either of the following two options, the first is considered preferable:
Expand Down
4 changes: 4 additions & 0 deletions docs/data/material/migration/migration-v4/migration-v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ This process is covered in [Migrating from JSS](/material-ui/migration/migrating
Need to refer back to an older version of the docs? Check out [the v4 documentation here](https://v4.mui.com/).
:::

:::info
If you are using Next.js and you are not sure how to configure SSR to work with both Emotion & JSS, take a look a this [example project](https://github.com/mui/material-ui/tree/master/examples/nextjs-with-typescript-v4-migration).
:::

## Why you should migrate

Material UI v5 includes many bug fixes and improvements over v4.
Expand Down
37 changes: 37 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel

# typescript
*.tsbuildinfo
42 changes: 42 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Material UI v5 and Next.js example with @mui/styles (in TypeScript)

## How to use

Download the example [or clone the repo](https://github.com/mui/material-ui):

<!-- #default-branch-switch -->

```sh
curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/nextjs-with-typescript
cd nextjs-with-typescript
```

Install it and run:

```sh
npm install
npm run dev
```

or:

<!-- #default-branch-switch -->

[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/mui/material-ui/tree/master/examples/nextjs-with-typescript-v4-migration)

[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/mui/material-ui/tree/master/examples/nextjs-with-typescript-v4-migration)

## The idea behind the example

The project uses [Next.js](https://github.com/vercel/next.js), which is a framework for server-rendered React apps.
It includes `@mui/material` and its peer dependencies, including `emotion`, the default style engine in Material UI v5. If you prefer, you can [use styled-components instead](https://mui.com/material-ui/guides/interoperability/#styled-components).
It also includes `@mui/styles`, the legacy styling solution that uses JSS as an engine.
It provides all the necessary config for working with both Emotion and JSS for server-side rendering.
The project is intended as a basic starter for migrating your application from v4 to v5, as it lets the JSS style overrides take precedence over the default styles passed to the components by Emotion.
It demonstrates what results after handling v5's breaking changes to the [theme](https://mui.com/material-ui/migration/v5-style-changes/) and [components](https://mui.com/material-ui/migration/v5-component-changes/).

## The Link component

Next.js has [a custom Link component](https://nextjs.org/docs/api-reference/next/link).
The example folder provides adapters for usage with Material UI.
You can find more information [in the documentation](https://mui.com/material-ui/guides/routing/#next-js).
5 changes: 5 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
4 changes: 4 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
};
31 changes: 31 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "nextjs-with-typescript-with-mui-styles",
"version": "5.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"post-update": "echo \"codesandbox preview only, need an update\" && yarn upgrade --latest"
},
"dependencies": {
"@emotion/cache": "^11.7.1",
"@emotion/react": "^11.9.0",
"@emotion/server": "^11.4.0",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "latest",
"@mui/material": "latest",
"@mui/styles": "latest",
"next": "12.1.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@types/node": "latest",
"@types/react": "17.0.2",
"eslint": "latest",
"eslint-config-next": "latest",
"typescript": "latest"
}
}
40 changes: 40 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import Head from 'next/head';
import { AppProps } from 'next/app';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { CacheProvider, EmotionCache } from '@emotion/react';
import theme from '../src/theme';
import createEmotionCache from '../src/createEmotionCache';

// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();

interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}

export default function MyApp(props: MyAppProps) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;

React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles?.parentElement?.removeChild(jssStyles);
}
}, []);

return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
130 changes: 130 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import * as React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import { ServerStyleSheets as JSSServerStyleSheets } from '@mui/styles';
import theme from '../src/theme';
import createEmotionCache from '../src/createEmotionCache';

export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
<link rel="shortcut icon" href="/static/favicon.ico" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
{/* Inject MUI styles first to match with the prepend: true configuration. */}
{(this.props as any).emotionStyleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

// You can find a benchmark of the available CSS minifiers under
// https://github.com/GoalSmashers/css-minification-benchmark
// We have found that clean-css is faster than cssnano but the output is larger.
// Waiting for https://github.com/cssinjs/jss/issues/279
// 4% slower but 12% smaller output than doing it in a single step.
//
// It's using .browserslistrc
let prefixer: any;
let cleanCSS: any;
if (process.env.NODE_ENV === 'production') {
/* eslint-disable global-require */
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const CleanCSS = require('clean-css');
/* eslint-enable global-require */

prefixer = postcss([autoprefixer]);
cleanCSS = new CleanCSS();
}

// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with static-site generation (SSG).
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

const originalRenderPage = ctx.renderPage;

// You can consider sharing the same emotion cache between all the SSR requests to speed up performance.
// However, be aware that it can have global side effects.
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
const jssSheets = new JSSServerStyleSheets();

ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App: any) =>
function EnhanceApp(props) {
return jssSheets.collect(<App emotionCache={cache} {...props} />);
},
});

const initialProps = await Document.getInitialProps(ctx);

// Generate style tags for the styles coming from Emotion
// This is important. It prevents Emotion from rendering invalid HTML.
// See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map((style) => (
<style
data-emotion={`${style.key} ${style.ids.join(' ')}`}
key={style.key}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));

// Gemerate the css string for the styles coming from jss
let css = jssSheets.toString();
// It might be undefined, e.g. after an error.
if (css && process.env.NODE_ENV === 'production') {
const result1 = await prefixer.process(css, { from: undefined });
css = result1.css;
css = cleanCSS.minify(css).styles;
}

return {
...initialProps,
styles: [
...emotionStyleTags,
<style
id="jss-server-side"
key="jss-server-side"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: css }}
/>,
...React.Children.toArray(initialProps.styles),
],
};
};
43 changes: 43 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/pages/about.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as React from 'react';
import type { NextPage } from 'next';
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { makeStyles } from '@mui/styles';
import Link from '../src/Link';
import ProTip from '../src/ProTip';
import Copyright from '../src/Copyright';

const useStyles = makeStyles((theme) => ({
main: {
marginTop: theme.spacing(4),
marginBottom: theme.spacing(4),
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
}));

const About: NextPage = () => {
const classes = useStyles();
return (
<Container maxWidth="lg">
<div className={classes.main}>
<Typography variant="h4" component="h1" gutterBottom>
MUI v5 + Next.js with TypeScript example
</Typography>
<Box maxWidth="sm">
<Button variant="contained" component={Link} noLinkStyle href="/">
Go to the home page
</Button>
</Box>
<ProTip />
<Copyright />
</div>
</Container>
);
};

export default About;
Loading