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 1 commit
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
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
48 changes: 48 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Next.js with TypeScript example with @mui/styles

## 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)

[![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)

## 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 MUI 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 configuring both emotion & JSS for server side rendering.
The project is intended as a basic starter while you are migration your application from v4 to v5, as it allows the JSS style overrides to take presendence over the styles coming by default for the Material UI components.

## 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 MUI.
More information [in the documentation](https://mui.com/material-ui/guides/routing/#next-js).

## What's next?

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

You now have a working example project.
You can head back to the documentation, continuing browsing it from the [templates](https://mui.com/material-ui/getting-started/templates/) section.
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": "^5.0.0",
"@mui/material": "^5.0.0",
"@mui/styles": "^5.0.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should mark them as latest?

Copy link
Member

@siriwatknp siriwatknp Jun 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samuelsycamore I wonder if we need to mention in the migration guide that React 18 upgrade should be done after MUI upgrade?

Currently, we only mention about the minimum version of React

cc @mnajdova does it make sense?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React 18 upgrade should be done after MUI upgrade

If you mean: React 18 upgrade should be done after @mui/styles is replaced with one of the alternatives, than yes, good point 👍

"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);

// Gemerate style tags for the styles coming from emotion
// This is important. It prevents emotion to render 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;
40 changes: 40 additions & 0 deletions examples/nextjs-with-typescript-v4-migration/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import type { NextPage } from 'next';
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography';
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 Home: 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>
<Link href="/about" color="secondary">
Go to the about page
</Link>
<ProTip />
<Copyright />
</div>
</Container>
);
};

export default Home;
Binary file not shown.
Loading