はじめに
Next.jsでMaterial-UIを利用する方法を紹介します。
Material-UIとは
Material-UIは、マテリアルデザインのReactコンポーネントを簡単に取り入れられるライブラリです。

Next.jsでMaterial-UIを使うと
特に何も気にせずNext.jsのアプリにMaterial-UIを取り入れると以下のようなエラーが出てきます。
公式のドキュメントには以下のように説明されています。
Material-UIは、サーバーでのレンダリングの制約を考慮してゼロから設計されましたが、正しく統合されるかどうかはユーザー次第です。 サーバサイドレンダリングでは必要とされるCSSが正しく渡される必要があります。(ドキュメントより)
つまり、SSR(サーバサイドレンダリング)に対応できるが、利用するときに正しくCSSを渡すように設定する必要があるみたいです。
Next.jsでMaterial-UIを使う方法
Next.jsアプリでMaterial-UIを使うときには下記のファイルに変更が必要です。
_app.js
_document.js
どちらもほぼMaterial-UIのリポジトリのサンプルコードをコピペするだけになります。
アプリの作成
まずはNext.jsのアプリを作成します。
❯ npx create-next-app material-ui-app --use-npm
Next.jsのアプリ作成についてはこちらでも解説しています。

Material-UIのインストール
npm
でMaterial-UIをインストールします。
❯ npm install @material-ui/core
コンポーネントを使う
index.js
にMaterial-UIのボタンを追加します。
今回利用したコンポーネントは下記になります。

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
},
},
}));
export default function Home() {
const classes = useStyles();
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
<p className={styles.description}>
Get started by editing{' '}
<code className={styles.code}>pages/index.js</code>
</p>
<div className={styles.grid}>
<a href="https://nextjs.org/docs" className={styles.card}>
<h2>Documentation →</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a href="https://nextjs.org/learn" className={styles.card}>
<h2>Learn →</h2>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
<a
href="https://github.com/vercel/next.js/tree/master/examples"
className={styles.card}
>
<h2>Examples →</h2>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
>
<h2>Deploy →</h2>
<p>
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
</a>
<div className={classes.root}>
<Button variant="contained">Default</Button>
<Button variant="contained" color="primary">
Primary
</Button>
<Button variant="contained" color="secondary">
Secondary
</Button>
<Button variant="contained" disabled>
Disabled
</Button>
<Button variant="contained" color="primary" href="#contained-buttons">
Link
</Button>
</div>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
)
}
theme.jsの作成
_app.js
と_document.js
で読み込むMaterial-UI全体に適用するテーマを作成します。
サンプルコードではsrc/
配下に格納されていましたが、ここでは一旦pages/
配下に保存します。
import { createTheme } from '@material-ui/core/styles';
// Create a theme instance.
const theme = createTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
},
});
export default theme;
_app.jsの作成
_app.js
を下記のように変更します。theme
のパスを変更しているだけでほぼコピペで大丈夫です。
import React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import theme from './theme';
export default function MyApp(props) {
const { Component, pageProps } = props;
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<React.Fragment>
<Head>
<title>My page</title>
<meta name="viewport" content="minimum-scale=1, 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>
</React.Fragment>
);
}
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired,
};
_document.jsの作成
_document.js
を作成します。こちらもtheme
のパスを変更しているだけで、ほぼコピペで大丈夫です。
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '@material-ui/core/styles';
import theme from './theme';
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="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side 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
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
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()],
};
};
確認
以上の設定でMaterial-UIがエラーなく使えるようになりました。テーマを追加しているので、ボタンの色も変更されています。
まとめ
Material-UIを利用するには下記のファイルを修正する必要がありました。
- _app.js
- _document.js
ただ、まだ課題があります。
_app.js
と_document.js
に余分な部分がまだある- ディレクトリ構造をどうしたらいいのか(theme.jsはどこに格納すべきか)
こちらは調べしだい記事にしたいと思います。