【始め方】Next.js + TypeScript + ESLint + Prettier Stylelint + Material UI + Emotion + depcheck + dependabot + Vercel で快適開発

## **はじめに**

Next.js で開発を始める際にやるべきことを記録しておきます。

最初の環境構築について、他にもいくつか記事を書いているので、よろしければこちらもご覧ください。

https://qiita.com/hasehiro0828/items/70e827c427d965d68e17

https://qiita.com/hasehiro0828/items/d8f1dd2a72c7999c9b76

あと、Next.js は公式ドキュメントがめちゃくちゃわかりやすいので、軽く目を通してみるのもいいと思います。

https://nextjs.org/

## **何はともあれ `create next-app`**

公式が出してくれてるのでこれを使わない手はないでしょう。

https://nextjs.org/docs/api-reference/create-next-app

```bash
yarn create next-app --typescript
```

## **node のバージョン指定**

### nodenv
anyenv で入れた nodenv を使用しています。

```bash
cd {{app}}
nodenv local nodenv local 16.13.0
yarn add -D @types/node@"16.11.6"
```

### package.json で engines を指定

```bash:package.json
...
  "engines": {
    "node": ">=16.13.0"
  },
...
```

## vercel CLI を使用する
vercel の CLI があるので、そちらを使用するようにします。
### インストール


## **ESLint, Prettier の設定**

**11.0.0** 以降、ESLint が標準で入るようになったみたいです。最高ですね👏

https://nextjs.org/docs/basic-features/eslint

ですので、Prettier の追加と ESLint で一部ルールの追加のみを行います。

具体的には、

- import の順番を決める
- 返り値の型を明示するルールを追加する
- prettier を導入し、フォーマットできるようになる
- 使用されていない import や変数で warning を出す

というようにします。

### **`@typescript-eslint/eslint-plugin` の追加**

返り値の型を明示的にするために入れます。

```bash
yarn add -D @typescript-eslint/eslint-plugin
```

### `eslint-plugin-unused-imports`の追加

使用していない import が入り込むのを防ぐために

https://github.com/sweepline/eslint-plugin-unused-imports

を追加します。

```bash
yarn add -D eslint-plugin-unused-imports
```

### **prettier の追加**

```bash
yarn add -D prettier eslint-config-prettier
```

次に、`prettierrc.js`と`.prettierignore`を追加します

```bash
echo "module.exports = {\\n\\tprintWidth: 120\\n}" > .prettierrc.js
echo ".next\\ntsconfig.json" > .prettierignore
```

### **`.eslintrc.json`の書き換え**

```json
{
  "extends": "next/core-web-vitals"
}
```

↓

```json
{
  "extends": ["next/core-web-vitals", "prettier"],
  "plugins": ["@typescript-eslint", "unused-imports"], // unused-imports を追加
  "rules": {
    "no-shadow": "error", // https://eslint.org/docs/rules/no-shadow
    "@typescript-eslint/explicit-function-return-type": "warn", // 返り値の型を明示するためのルール
    "unused-imports/no-unused-imports": "error",
    "unused-imports/no-unused-vars": [
      "warn",
      { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
    ],
    "import/order": [ // import の順番を決めます
      "error",
      {
        "groups": ["builtin", "external", "parent", "sibling", "index", "object", "type"],
        // pathGroups: [
        //   {
        //     pattern: "@alias/**",
        //     group: "parent",
        //     position: "before",
        //   },
        // ],
        "alphabetize": {
          "order": "asc"
        },
        "newlines-between": "always"
      }
    ]
  }
}
```

### **`package.json`の `scripts`に追記**

```json
"scripts": {
  // ...
  "format": "prettier --write './**/*.{js,jsx,ts,tsx,json}'"
},
```

### **format & lint**

```bash
yarn format
yarn lint --fix
```

ESLint で 自動で fix 出来ない Warning が出ていればそれも修正します。

## Stylelint の導入
https://stylelint.io/user-guide/get-started


### `stylelint`, `stylelint-config-standard`, `stylelint-config-idiomatic-order`, `stylelint-prettier`, `stylelint-config-prettier` をインストール
https://github.com/prettier/stylelint-prettier

https://github.com/ream88/stylelint-config-idiomatic-order

`stylelint-config-idiomatic-order` は css の順番を決めてくれます。

```bash
yarn add -D stylelint stylelint-config-standard stylelint-config-idiomatic-order stylelint-prettier stylelint-config-prettier
```

### `.stylelintrc.json` を作成

```bash
echo '{\n\t"extends": ["stylelint-config-standard", "stylelint-config-idiomatic-order", "stylelint-prettier/recommended"]\n}' > .stylelintrc.json
```

### VSCode の拡張機能を追加
https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint

を使用します。
設定には以下を追加します。

```json
"settings": {
  // ...
  "css.validate": false,
  "less.validate": false,
  "scss.validate": false,
  "editor.codeActionsOnSave": {
    "source.fixAll.stylelint": true
  }
}
```

## **フォルダ構成等変更**

### **`src`下に移動**

`pages`ディレクトリや`styles`ディレクトリを`src`下に置きます。

`src`下に移動させても、何も設定する必要はありません。

https://nextjs.org/docs/advanced-features/src-directory

```bash
mkdir src
mv pages styles src
```

### **Absolute Imports にする**

`src`を`@`として import できるようにします。

https://nextjs.org/docs/advanced-features/module-path-aliases

`tsconfig.json` を変更します。

```json
{
  // ...
  "baseUrl": ".",
  "paths": {
    "@/*": ["src/*"]
  },
  // ...
}
```

ファイル内で相対パスで import している部分を `@`を使うように変更します。

### `/pages` に page 以外のファイルも置けるようにする

https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions#including-non-page-files-in-the-pages-directory

こちらを参考に `.pages.tsx` などの拡張子を使うように設定します。

## Material UI(v5) の導入

https://mui.com/getting-started/installation/

Material UI だけでなく、Emotion も使いたいと思っていたのですが、v5 から Material UI が Emotion を使うようになるようです。

~~ですので、2021-09-03 段階ではまだ pre-release ですが、 v5 を使ってみます。~~
2021-09-16 に v5 が正式にリリースされたようです👏👏

https://github.com/mui-org/material-ui/tree/next/examples/nextjs-with-typescript

公式の example も参考にします。

ちなみに、以下で書かれている変更を加えても反映されないことがありますが、その場合はルートの `.next`フォルダを消してしまえば反映されます。

ちゃんと調べたわけではないのでおそらくですが、設定ファイルだけ変更した時などに変更が反映されない場合があるのかな？と思っています。

### Materiai UI のインストール

```bash
yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled @emotion/cache @emotion/server
```

### `theme.ts`,`createEmotionCache.ts` ファイルの作成

```bash
rm -rf src/styles
mkdir src/styles
touch src/styles/theme.ts src/styles/createEmotionCache.ts
```

https://github.com/mui-org/material-ui/blob/next/examples/nextjs-with-typescript/src/theme.ts

https://github.com/mui-org/material-ui/blob/next/examples/nextjs-with-typescript/src/createEmotionCache.ts

をそのままコピーしてきます。（型を多少つけました）

```ts:theme.ts
import { red } from "@mui/material/colors";
import { createTheme } from "@mui/material/styles";

// Create a theme instance.
const theme = createTheme({
  palette: {
    primary: {
      main: "#556cd6",
    },
    secondary: {
      main: "#19857b",
    },
    error: {
      main: red.A400,
    },
  },
});

export default theme;
```

```ts:createEmotionCache.ts
import createCache, { EmotionCache } from "@emotion/cache";

export default function createEmotionCache(): EmotionCache {
  return createCache({ key: "css" });
}
```

### `Link.tsx`の作成

https://github.com/mui-org/material-ui/blob/next/examples/nextjs-with-typescript/src/Link.tsx

を作成します。

Next の Link コンポーネントと Material UI の Link コンポーネントをいい感じに使うためのものです。

```bash
mkdir src/components
touch src/components/Link.tsx
```

```tsx
import MuiLink, { LinkProps as MuiLinkProps } from "@mui/material/Link";
import clsx from "clsx";
import NextLink, { LinkProps as NextLinkProps } from "next/link";
import { useRouter } from "next/router";
import * as React from "react";

interface NextLinkComposedProps
  extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href">,
    Omit<NextLinkProps, "href" | "as"> {
  to: NextLinkProps["href"];
  linkAs?: NextLinkProps["as"];
  href?: NextLinkProps["href"];
}

export const NextLinkComposed = React.forwardRef<HTMLAnchorElement, NextLinkComposedProps>(function NextLinkComposed(
  props,
  ref
) {
  const { to, linkAs, href, replace, scroll, passHref, shallow, prefetch, locale, ...other } = props;

  return (
    <NextLink
      href={to}
      prefetch={prefetch}
      as={linkAs}
      replace={replace}
      scroll={scroll}
      shallow={shallow}
      passHref={passHref}
      locale={locale}
    >
      <a ref={ref} {...other} />
    </NextLink>
  );
});

export type LinkProps = {
  activeClassName?: string;
  as?: NextLinkProps["as"];
  href: NextLinkProps["href"];
  noLinkStyle?: boolean;
} & Omit<NextLinkComposedProps, "to" | "linkAs" | "href"> &
  Omit<MuiLinkProps, "href">;

// A styled version of the Next.js Link component:
// https://nextjs.org/docs/#with-link
const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(function Link(props, ref) {
  const {
    activeClassName = "active",
    as: linkAs,
    className: classNameProps,
    href,
    noLinkStyle,
    role, // Link don't have roles.
    ...other
  } = props;

  const router = useRouter();
  const pathname = typeof href === "string" ? href : href.pathname;
  const className = clsx(classNameProps, {
    [activeClassName]: router.pathname === pathname && activeClassName,
  });

  const isExternal = typeof href === "string" && (href.indexOf("http") === 0 || href.indexOf("mailto:") === 0);

  if (isExternal) {
    if (noLinkStyle) {
      return <a className={className} href={href as string} ref={ref as any} {...other} />;
    }

    return <MuiLink className={className} href={href as string} ref={ref} {...other} />;
  }

  if (noLinkStyle) {
    return <NextLinkComposed className={className} ref={ref as any} to={href} {...other} />;
  }

  return <MuiLink component={NextLinkComposed} linkAs={linkAs} className={className} ref={ref} to={href} {...other} />;
});

export default Link;
```

### `_document.tsx`の作成

https://github.com/mui-org/material-ui/blob/next/examples/nextjs-with-typescript/pages/_document.tsx

を作成します。

```bash
touch src/pages/_document.tsx
```

基本的に上の例そのままですが、

- lang の変更
- 型付け
- import 周り少し修正

などの変更を行なっています。

```tsx
import createEmotionServer from "@emotion/server/create-instance";
import { RenderPageResult } from "next/dist/shared/lib/utils";
import Document, { Html, Head, Main, NextScript, DocumentInitialProps } from "next/document";
import * as React from "react";

import createEmotionCache from "@/styles/createEmotionCache";
import theme from "@/styles/theme";

export default class MyDocument extends Document {
  render(): JSX.Element {
    return (
      <Html lang="ja">
        <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 static-site generation (SSG).
MyDocument.getInitialProps = async (ctx): Promise<DocumentInitialProps> => {
  // 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);

  ctx.renderPage = (): RenderPageResult | Promise<RenderPageResult> =>
    originalRenderPage({
      enhanceApp:
        (App: any) =>
        // eslint-disable-next-line react/display-name
        (props): JSX.Element =>
          <App emotionCache={cache} {...props} />,
    });

  const initialProps = await Document.getInitialProps(ctx);
  // This is important. It prevents emotion to render invalid HTML.
  // See https://github.com/mui-org/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 }}
    />
  ));

  return {
    ...initialProps,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: [...React.Children.toArray(initialProps.styles), ...emotionStyleTags],
  };
};
```

### `_app.tsx`の変更

https://github.com/mui-org/material-ui/blob/next/examples/nextjs-with-typescript/pages/_app.tsx

をコピーしてきます。

Head は削除しました。

```tsx
import { CacheProvider, EmotionCache } from "@emotion/react";
import CssBaseline from "@mui/material/CssBaseline";
import { ThemeProvider } from "@mui/material/styles";
import { AppProps } from "next/app";
import Head from "next/head";
import * as React from "react";

import createEmotionCache from "@/styles/createEmotionCache";
import theme from "@/styles/theme";

// 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): JSX.Element {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
  return (
    <CacheProvider value={emotionCache}>
      <ThemeProvider theme={theme}>
        {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </CacheProvider>
  );
}
```

## `index.tsx`の変更

https://github.com/mui-org/material-ui/blob/next/examples/nextjs-with-typescript/pages/index.tsx

をコピーしてきます。

若干修正しています。

```tsx
import { Box, Container, Typography } from "@mui/material";

import type { NextPage } from "next";

import Link from "@/components/Link";

const Index: NextPage = () => {
  return (
    <Container maxWidth="sm">
      <Box sx={{ my: 4 }}>
        <Typography variant="h4" component="h1" gutterBottom>
          Next.js v5-beta with TypeScript example
        </Typography>

        <Link href="/about" color="secondary">
          Go to the about page
        </Link>
      </Box>
    </Container>
  );
};

export default Index;
```

### `about.tsx`の作成

https://github.com/mui-org/material-ui/blob/next/examples/nextjs-with-typescript/pages/about.tsx

を作成します。

若干変更を加えています。

```bash
touch src/pages/about.tsx
```

```tsx
import { Box, Button, Container, Typography } from "@mui/material";
import { NextPage } from "next";

import Link from "@/components/Link";

const About: NextPage = () => {
  return (
    <Container maxWidth="sm">
      <Box sx={{ my: 4 }}>
        <Typography variant="h4" component="h1" gutterBottom>
          Next.js v5-beta with TypeScript example
        </Typography>
        <Button variant="contained" component={Link} noLinkStyle href="/">
          Go to the main page
        </Button>
      </Box>
    </Container>
  );
};

export default About;
```

## Emotion でのスタイリング

Emotion で `css` プロパティ を使用してスタイリングをするために、少し設定等を修正します。

https://github.com/vercel/next.js/tree/master/examples/with-emotion

などを参考にしています。

ちなみに、`css` プロパティを使いたい理由は

- 「styled-component のやり方だと、定義したコンポーネントなのか、スタイルがつけられているだけなのかパッとわからない」というのを避けたかった
- Material UI の `makeStyles` を使用する方法ではコードジャンプが出来ない

などです。

Material UI の v4 であれば、`<StylesProvider injectFirst>`を`_app.tsx`に追加しなければいけなかったのですが、v5 からは要らなくなったようです。

### `@emotion/babel-plugin` のインストール

```bash
yarn add -D @emotion/babel-plugin
```

### `.babelrc`の作成

https://github.com/vercel/next.js/blob/master/examples/with-emotion/.babelrc

を作成します。

```bash
touch .babelrc
```

```.babelrc
{
  "presets": [
    [
      "next/babel",
      {
        "preset-react": {
          "runtime": "automatic",
          "importSource": "@emotion/react"
        }
      }
    ]
  ],
  "plugins": ["@emotion/babel-plugin"]
}
```

### `tsconfig.ts`の修正

https://emotion.sh/docs/typescript#css-prop

を参考にします。

```json
{
  "compilerOptions": {
    // ...
    "jsx": "preserve",
    "jsxImportSource": "@emotion/react", //追加
    // ...
  },
  // ..
}
```

### `index.tsx`の変更

スタイリングできるか試してみます。

```tsx
import { css } from "@emotion/react";
import { Box, Container, Typography, Button } from "@mui/material";

import type { NextPage } from "next";

import Link from "@/components/Link";

const yellowButtonCss = css`
  color: green;
  background-color: yellow;

  :hover {
    background-color: #919100;
  }
`;

const Index: NextPage = () => {
  return (
    <Container maxWidth="sm">
      <Box sx={{ my: 4 }}>
        <Typography variant="h4" component="h1" gutterBottom>
          Next.js v5-beta with TypeScript example
        </Typography>

        <Link href="/about" color="secondary">
          Go to the about page
        </Link>
      </Box>
      <Box>
        <Button variant="contained" color="primary">
          Css
        </Button>
        <Button variant="contained" css={yellowButtonCss}>
          Css
        </Button>
      </Box>
    </Container>
  );
};

export default Index;
```

`yarn dev` 実行後http://localhost:3000/ にアクセスすると、下のような画面が表示されるはずです🙆‍♂️

![スクリーンショット 2021-09-04 18.53.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209015/6e00324b-2a0e-5a45-923d-24d8c29c56b7.png)

## depcheck, husky の導入
https://qiita.com/hasehiro0828/items/70e827c427d965d68e17#depcheck-husky-lint-staged-%E3%81%AE%E5%B0%8E%E5%85%A5

では lint-staged も導入したのですが、今回は一旦入れません。

一人で開発をしている内は、prettier・ESLint のどちらもエディタで適用されるので、意味もなくlint-staged が走っている時間が無駄だと思ったからです。

以下の設定をすることで、使用していないパッケージが入り込んだり、不要になったパッケージがいつまでも残ることがなくなります🙆‍♂️

### depcheck のインストール & 設定
https://github.com/depcheck/depcheck

グローバルに入れたくないなぁと思ったので、devDependencies としてインストールします。

```bash
yarn add -D depcheck
```

設定ファイル(`.depcheckrc`)を作成する。
こちらは適宜修正してください。

```bash
echo 'ignores: ["@mui/icons-material", "@types/node", "depcheck", "stylelint*", "clsx"]\nignore-patterns: [".history", ".vscode", "dist", "node_modules"]' > .depcheckrc
```

### husky のインストール & 設定
https://typicode.github.io/husky/#/?id=automatic-recommended

```bash
npx husky-init && yarn
```

`.husky/pre-commit`を書き換えます。

```bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

eval "$(nodenv init -)" # git の GUI クライアントで node を正しく読み込んでくれない問題の対策
yarn run depcheck
```

`.husky/pre-push`を作成します。

```bash
yarn husky add .husky/pre-push
```

```bash:.husky/pre-push
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

eval "$(nodenv init -)" # git の GUI クライアントで node を正しく読み込んでくれない問題の対策
yarn lint --max-warnings 0
```

## `vercel.json` を作成する
Vercel がメッセージを書くたびに、github の notification に通知が来るので、コメントしないように設定します。

https://vercel.com/support/articles/how-to-prevent-vercel-github-comments

```bash
echo '{\n\t"github": {\n\t\t"silent": true\n\t}\n}' > vercel.json
```


##  デプロイ

https://nextjs.org/learn/basics/deploying-nextjs-app

公式ドキュメントの通りに、Vercelにデプロイします。

簡単な作業を数回するだけでデプロイすることができました👏

プルリクを作成すればプレビューサイトも作成してくれるみたいですね、最高です。

## Dependabot の導入

ついでに Dependabot も入れてみます。

### Dependabot を Enable する

![スクリーンショット 2021-09-05 15.26.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209015/a6820b1e-9fb1-7178-de28-ce1750aec463.png)

GitHub の画面から `Enable Dependabot` を選択します。

### Config ファイルの作成

![スクリーンショット 2021-09-05 15.27.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209015/8bc723e1-ed91-054e-95ed-6bf95c7e201b.png)

続いて、`Create config file` を選択します。

`dependabot.yaml`の作成画面に遷移するので、説明とドキュメントを読んで設定します。

私の場合、今回はこんな感じになりました。

```yaml
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
  - package-ecosystem: "npm" # See documentation for possible values
    directory: "/" # Location of package manifests
    schedule:
      interval: "weekly"
      day: "saturday"
      time: "09:00"
      timezone: "Asia/Tokyo"
    reviewers:
      - "hasehiro0828"
```

### `Security & analysis` を有効化

![スクリーンショット 2021-09-05 15.37.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209015/5e1c2760-91bb-c780-4ff1-121144f10bb7.png)

これらの設定を全て `Enabled`にしました。

### dependabot が作成するプルリクはプレビューを作成しないようにする

デフォルトの状態では、dependabot が作成するプルリク全てでプレビューが作成されてしまうので、dependabot のプルリクは無視するように設定します。

https://github.com/vercel/vercel/issues/3166#issuecomment-832870464

https://vercel.com/support/articles/how-do-i-use-the-ignored-build-step-field-on-vercel

#### `ignoreBranchScript.sh`の作成

```bash
touch ignoreBranchScript.sh
```

```bash
#!/bin/bash

# dependabot が作成したブランチはデプロイしない
echo "📝 VERCEL_GIT_COMMIT_REF: $VERCEL_GIT_COMMIT_REF"
if [[ `echo "$VERCEL_GIT_COMMIT_REF" | grep "dependabot/"` ]]; then 
  exit 0; 
fi

# dependabot が作成したブランチがマージされた時はデプロイしない
echo "📝 VERCEL_GIT_COMMIT_AUTHOR_LOGIN: $VERCEL_GIT_COMMIT_AUTHOR_LOGIN"
if [[ `echo "$VERCEL_GIT_COMMIT_AUTHOR_LOGIN" | grep "dependabot"` ]]; then 
  exit 0; 
fi
exit 1;
```

#### `Ignored Build Step`の設定

Vercel のプロジェクトの `Settings > Git`に移動します。

`Ignored Build Step`に以下を入力します。

```bash
bash ignoreBranchScript.sh
```

### Dependabot が作成したプルリクを自動でマージする
https://github.com/marketplace/actions/dependabot-auto-merge

を使用します。

https://github.com/settings/tokens

で `Personal access token` を作成する。

リポジトリの `/settings/secrets/actions/new` で `DEPENDABOT_AUTO_MERGE_TOKEN` という名前で先ほど作成した token を登録する。

#### `.github/auto-merge.yml`を作成する
https://github.com/ahmadnassri/action-dependabot-auto-merge#configuration-file-syntax

```yml:.github/auto-merge.yml
- match:
    dependency_type: development
    update_type: all

- match:
    dependency_type: production
    update_type: security:minor # includes patch updates!

- match:
    dependency_type: production
    update_type: semver:patch
```

#### `.github/workflows/main.yml`を作成する

```yml:.github/workflows/main.yml
name: Auto merge pull request from dependabot

on:
  pull_request_target:

jobs:
  auto-merge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Dependabot Auto Merge
        uses: ahmadnassri/action-dependabot-auto-merge@v2.3.1
        with:
          # The GitHub token used to merge the pull-request
          github-token: ${{ secrets.DEPENDABOT_AUTO_MERGE_TOKEN }}
```


## おわりに

以上で、Next.js で開発を快適に進めていくための環境構築が完了しました👏👏

開発を開始してからコードの品質を担保しようとすると結構大変になってしまうので、最初の土台作りが肝心だと思っています。

建物をいくらまっすぐに建てることができても、土台が傾いてしまっていてはいずれ倒壊してしまいますからね；；

自分なりのテンプレートをある程度拘って作って、あとはガンガン開発していきましょう💪

## おまけ ー Material UI (v4)の導入

最初は v4 での環境構築をしていたので、折角なので残しておきます。

### Materiai UI のインストール

```bash
yarn add @material-ui/core @material-ui/icons
```

### `theme.ts` ファイルの作成

```bash
rm -rf src/styles
mkdir src/styles
touch src/styles/theme.ts
```

https://github.com/mui-org/material-ui/blob/master/examples/nextjs/src/theme.js

をそのままコピーしてきます。

```ts
import { red } from "@material-ui/core/colors";
import { createTheme } from "@material-ui/core/styles";

// Create a theme instance.
const theme = createTheme({
  palette: {
    primary: {
      main: "#556cd6",
    },
    secondary: {
      main: "#19857b",
    },
    error: {
      main: red.A400,
    },
    background: {
      default: "#fff",
    },
  },
});

export default theme;
```

### `_document.tsx`の作成

https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_document.js

を作成します。

```bash
touch src/pages/_document.tsx
```

`lang`を変えたり、型をつけたりしていますが、基本的に上の例そのままです。

```tsx
import { ServerStyleSheets } from "@material-ui/core/styles";
import { RenderPageResult } from "next/dist/shared/lib/utils";
import Document, { Html, Head, Main, NextScript, DocumentInitialProps } from "next/document";
import React from "react";

import theme from "@/styles/theme";

export default class MyDocument extends Document {
  render(): JSX.Element {
    return (
      <Html lang="ja">
        <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" />
          <link rel="icon" href="/favicon.ico" />
        </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): Promise<DocumentInitialProps> => {
  // 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 = (): RenderPageResult | Promise<RenderPageResult> =>
    originalRenderPage({
      // eslint-disable-next-line react/display-name, @typescript-eslint/explicit-function-return-type
      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()],
  };
};

```

### `_app.tsx`の変更

https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_app.js

```tsx
import CssBaseline from "@material-ui/core/CssBaseline";
import { ThemeProvider } from "@material-ui/core/styles";
import React, { useEffect } from "react";

import type { AppProps } from "next/app";

import theme from "@/styles/theme";

function MyApp({ Component, pageProps }: AppProps): JSX.Element {
  useEffect(() => {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector("#jss-server-side");
    if (jssStyles && jssStyles.parentElement) {
      jssStyles.parentElement.removeChild(jssStyles);
    }
  }, []);

  return (
    <ThemeProvider theme={theme}>
      {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
      <CssBaseline />
      <Component {...pageProps} />
    </ThemeProvider>
  );
}
export default MyApp;
```

### `index.tsx`の変更

```tsx
import { Container, Box, Typography, Button } from "@material-ui/core";
import Head from "next/head";

import type { NextPage } from "next";

const Index: NextPage = () => {
  return (
    <>
      <Head>
        <title>Index</title>
        <meta name="description" content="Index Page" />
      </Head>

      <main>
        <Container maxWidth="sm">
          <Box my={4}>
            <Typography variant="h4" component="h1" gutterBottom>
              Next.js example
            </Typography>

            <Button variant="contained" color="primary">
              primary
            </Button>
            <Button variant="contained" color="secondary">
              secondary
            </Button>
          </Box>
        </Container>
      </main>
    </>
  );
};

export default Index;
```

### `Warning: Prop 'className' did not match`について

上記の変更後`yarn dev`を実行すると、`Warning: Prop 'className' did not match. Server: "MuiBox-root MuiBox-root-1" Client: "MuiBox-root MuiBox-root-2"`といったエラーが発生します。

このエラーが発生しないようにするのが、`_app.tsx`の`useEffect`などでの処理だと思うのですが、上手くいきません。調べてもそれっぽい情報が見つかりませんでした。

`babel-plugin-styled-components`を使うみたいな方法はあったのですが、改善しませんでした。

最近のアップデートによる影響かなぁと思い、一旦スルーしました。（v5 ではエラーが出ませんでした）

## **参考にさせていただいたサイト**

https://nextjs.org/

https://fwywd.com/tech/next-eslint-prettier

https://qiita.com/282Haniwa/items/243f00c39ee7c992d7f7