Next.jsでMaterial-UIを使う

はじめに

Next.jsでMaterial-UIを利用する方法を紹介します。

Material-UIとは

Material-UIは、マテリアルデザインのReactコンポーネントを簡単に取り入れられるライブラリです。

Material-UI: A popular React UI framework
React components for faster and easier web development. Build your own design system, or start with Material Design.

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のリポジトリのサンプルコードをコピペするだけになります。

material-ui/examples/nextjs at master · mui-org/material-ui
Material-UI is a simple and customizable component library to build faster, beautiful, and more accessible React applications. Follow your own design system, or...

アプリの作成

まずはNext.jsのアプリを作成します。

❯ npx create-next-app material-ui-app --use-npm

Next.jsのアプリ作成についてはこちらでも解説しています。

【Next.js初めの一歩】アプリを作成する
はじめにNext.jsでアプリを作成する方法を紹介します。Next.jsでアプリ作成手動で作成することもできますが、create-next-appを利用して自動でアプリを作成することが推奨されています。npmでアプリを作成するには以...

Material-UIのインストール

npmでMaterial-UIをインストールします。

❯ npm install @material-ui/core

コンポーネントを使う

index.jsにMaterial-UIのボタンを追加します。

今回利用したコンポーネントは下記になります。

React Button component - Material-UI
Buttons allow users to take actions, and make choices, with a single tap.
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のパスを変更しているだけでほぼコピペで大丈夫です。

material-ui/_app.js at master · mui-org/material-ui
Material-UI is a simple and customizable component library to build faster, beautiful, and more accessible React applications. Follow your own design system, or...
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のパスを変更しているだけで、ほぼコピペで大丈夫です。

material-ui/_document.js at master · mui-org/material-ui
Material-UI is a simple and customizable component library to build faster, beautiful, and more accessible React applications. Follow your own design system, or...
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はどこに格納すべきか)

こちらは調べしだい記事にしたいと思います。

参考

タイトルとURLをコピーしました