Next.js × TypeScript × styled-components × material-uiで環境構築をしてみる

趣味でnextjsを始めて、環境構築を完了するまで時間を要したので書いていきます。

## アプリケーションを作成
公式でTypeScriptとstyled-componentsが既にはいっているリポジトリが存在するのでそちらを使います。


```bash:terminal
yarn create next-app --example with-typescript my-app
```

## material-uiのインストール
material-uiをyarnでインストールします。

```bash:terminal
yarn add @material-ui/core
```


## babelファイルを作成
SSRのサポート、デバッグサポートを追加するためにbabelファイルの作成・修正を行っていきます。

```bash:terminal
yarn add --dev babel-plugin-styled-components
```

```"":.babelrc
{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true, "displayName": true, "preprocess": false }]]
}
```

## _document.tsx, _app.tsxを修正する

### _document.tsx
`material-ui`と`styled-components`がSSR時にも適切に反映されるようにするために`_document.tsx`を拡張していきます。

```tsx:pages/_document.tsx
import Document, { DocumentContext, Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet as StyledComponentSheets } from 'styled-components';
import { ServerStyleSheets as MaterialUiServerStyleSheets } from '@material-ui/styles';

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const styledComponentSheets = new StyledComponentSheets();
    const materialUiServerStyleSheets = new MaterialUiServerStyleSheets();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props =>
            styledComponentSheets.collectStyles(
              materialUiServerStyleSheets.collect(<App {...props} />)
            )
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {styledComponentSheets.getStyleElement()}
            {materialUiServerStyleSheets.getStyleElement()}
          </>
        )
      };
    } finally {
      styledComponentSheets.seal();
    }
  }

  render() {
    return (
      <Html lang="ja">
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}
```

### _app.tsx
material-uiのtheme設定に関しては、別ファイルからimportしていますが、`_app.tsx`内に記述しても問題ないかと思います。

```tsx:pages/_app.tsx
import React, { useEffect } from 'react';
import { AppProps } from 'next/app';
import { ThemeProvider as StyledComponentsThemeProvider } from 'styled-components';
import { ThemeProvider as MaterialUIThemeProvider } from '@material-ui/core/styles';
import { StylesProvider } from '@material-ui/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import Layout from './components/page/layout';
import theme from './components/theme';

const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {

  useEffect(() => {
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
      jssStyles.parentElement.removeChild(jssStyles);
    }
  }, []);

  return (
    <StylesProvider injectFirst>
      <MaterialUIThemeProvider theme={theme}>
        <StyledComponentsThemeProvider theme={theme}>
          <CssBaseline />
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </StyledComponentsThemeProvider>
      </MaterialUIThemeProvider>
    </StylesProvider>
  );
};

export default MyApp;
```


## 以上

```bash:terminal
yarn dev
```

こちらで以上になります。
お疲れ様でした。

## 参考リンク
- [nextjs-typescript-styled-component | repo](https://github.com/zapkub/nextjs-typescript-styled-component)
- [SSR with Next.js, styled-components and Material UI](https://medium.com/javascript-in-plain-english/ssr-with-next-js-styled-components-and-material-ui-b1e88ac11dfa)
- [Customizing renderPage | Next.js](https://nextjs.org/docs/advanced-features/custom-document#customizing-renderpage)
- [Server Rendering | material-ui](https://material-ui.com/guides/server-rendering/)
- [babel](https://styled-components.com/docs/tooling#babel-plugin)
- [Next.js x TypeScript x styled-components x Material-UI x Apollo で開発を始める](https://athlingual.com/blog/how-to-start-with-nextjs-typescript-styledcomponents-materialui-apollo)