Next.js周りのボイラープレート(Typescript + Jest + Storybook + ESLint + Prettier + styled-components)

趣味で最近Next.jsを触っています。
業務ではReactを扱うことがあるのですが、タイトルに載せているような、普段業務で使っているライブラリの入れ方がまとまっているところはなかったので、まとめてみました。

尚、本記事で扱うライブラリのバージョンは以下のようになっています。

- yarn 1.22.15
- Next.js 11.1.2
- React 17.0.2
- Typescript 4.4.3
- Jest 27.2.4
- Storybook 6.3.9
- ESLint 7.32.0
- Prettier 2.4.1
- styled-components 5.3.1

## プロジェクト作成(with Next.js, ESLint, Typescript)

以前はNextを個別に入れる形になっていたのですが、現在Next.jsの方から[テンプレート](https://nextjs.org/docs/api-reference/create-next-app)が提供されています。
これにはNext.js / ReactだけでなくESLintも入っています。(以前はESLintが色々大変だった……) 
しかもTypescriptも最初から入っているものも提供されています。

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

## Next.jsの起動
上記手順を行うと `package.json` にいくつかのnpm scriptsが登録されています。

```package.json
"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start",
  "lint": "next lint"
}
```

`yarn dev`と打って`localhost:3000`でNext.jsのサーバーが立っていれば成功です。

個人的な好みもあるのですが、ソースファイルは `src` ディレクトリ以下に置きたいと思っています。
[公式でこちら対応](https://nextjs.org/docs/advanced-features/src-directory)されており、必要なディレクトリを移動させるだけです。

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

## Typescript

基本的な設定(必要なライブラリのインストール, 設定ファイルの作成)は最初の手順で行われています。
個人の好みもありますが、Typescriptの恩恵をフルで受けるために`tsconfig.json`の以下のフィールドを変更します。

```
// true -> false に変更
"allowJS": false
```

## Jest
Typescript環境下でJestを動かす方法はいくつかありますが、`ts-jest`を使います。
必要なライブラリをインストールします。

```
yarn add -D jest  ts-jest @types/jest
```

Jestの実行に必要なファイルを生成します。

```
yarn ts-jest config:init
```

`jest.config.js`が出力されているはずです。
確認のためにテスト用のファイルを作ります。

```
mkdir src/util
```

```src/util/sum.ts
const sum = (a: number, b: number) => a + b;

export { sum };
```

```src/util/sum.test.ts
import { sum } from "./sum";

test("adds 1 + 2 to equal 3", () => {
  expect(sum(1, 2)).toBe(3);
});
```

Jestを動かせるように`package.json`を変更します。

```package.json
"scripts": {
    "test": "jest"
}
```

テストを動かしてみます。

```
yarn test
```

テストが動いて通っていることが確認できました。

## Storybook
Storybookの[公式ページ](https://storybook.js.org/docs/react/get-started/install)を参考にしています。
以前は必要なライブラリを個別にインストールする形だったのですが、公式からプロジェクトの構成を判別してライブラリ・設定ファイルを生成してくれるスクリプトが用意されています。

```
npx sb init
```

storybookを動かすコマンドがscriptsに記載されています。

```package.json
"scripts": {
  "storybook": "start-storybook -p 6006",
  "build-storybook": "build-storybook"
}
```

また、storybookのconfigファイルも自動生成されています。
(昔はwebpack.config.jsとか自分で書く必要があって大変だった……)

賢いことに `src` ディレクトリの有無で生成するconfigが変わっています。
今回紹介するのは `src` ディレクトリ付きです。

```
mkdir .storybook
```

```..storybook/main.js
module.exports = {
  "stories": [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials"
  ]
}
```

```..storybook/preview.js
export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}
```

上記コマンドでサンプル用のファイルが作成されているのですが、せっかくなので新規でstory用のファイルを作成します。

```
mkdir src/components
```

```src/components/Text.tsx
type Props = {
  text: string;
};

const Text = ({ text }: Props) => <span>{text}</span>;

export { Text };
```

```src/components/Text.stories.tsx
import { ComponentMeta, ComponentStory } from '@storybook/react'
import { Text } from './Text'

export default {
  title: 'Text',
  component: Text,
  argTypes: {
    text: {
      control: { type: 'text' },
    },
  },
} as ComponentMeta<typeof Text>

const Template: ComponentStory<typeof Text> = (args) => <Text {...args} />

export const HelloText = Template.bind({})
HelloText.args = {
  text: 'hello!',
}
```

`yarn storybook`とコマンドを打ってStorybookが立ち上がります。
また、staticなファイルを生成したい場合は `yarn build-storybook` で `storybook-static` ディレクトリ配下に生成されます。

## ESLint

最初のプロジェクト生成で必要なものが生成されます。

```package.json
{
  "scripts": {
    "lint": "next lint"
  },
  "devDependencies": {
    "eslint": "7.32.0",
    "eslint-config-next": "11.1.2",
  }
}
```

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

これによって設定されるルールは公式の[このページ](https://nextjs.org/docs/basic-features/eslint#eslint-plugin)に書かれています。

## Prettier

必要なライブラリをインストールします。

```
yarn add -D prettier --exact
```

`--exact`は公式で推奨されているので付けています。

フォーマットしやすいようにコマンドを書きます。

```package.json
"scripts": {
  "format": "prettier --write src/**/*.{ts,tsx}"
}
```

prettierの設定ファイルを書きます。
今回は[next.jsが使っているもの](https://github.com/vercel/next.js/blob/canary/.prettierrc.json)をベースに個人的な好みの `trailingComma: es5` を追加します。

```.prettierrc
{
  // 上2つはnext.jsのリポジトリのものと同一です
  "singleQuote": true,
  "semi": false,
  "trailingComma": "es5"
}
```

## styled-components

Next.jsでCSSを使う方法はいくつかありますが、私は業務で使っていた[styled-components](https://styled-components.com/)に慣れていたので、それを使えるようにセットアップします。

必要なライブラリをインストールします。

```
yarn add styled-components
yarn add -D @types/styled-components babel-plugin-styled-components
```

サーバーサイドレンダリングに必要な設定を入れます。
[こちら](https://github.com/vercel/next.js/tree/master/examples/with-styled-components)を参考にしています。

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

```jsx:src/pages/_document.tsx
import Document, { DocumentContext } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        })

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

`styled-components`を使ったサーバーサイドレンダリングが出来ているかを確認します。

```src/pages/index.tsx
import styled from "styled-components";

const Home = () => <H1>Hello world!</H1>;

export default Home;

const H1 = styled.h1`
  color: #6495ed;
`;
```

`yarn dev`で立ち上げたNext.jsのサーバににアクセスした際に、開発者コンソール等で、レスポンス時のHTML上で既に色が付いていることが分かると思います。

## 最後に

自分のための備忘録的なものですが、お役に立てれば幸いです。
今回のプロジェクトは[ここ](https://github.com/daku10/nextjs-boilerplate)で公開しています。

## 参考

- [Next.js](https://nextjs.org/docs)
- [Jest](https://jestjs.io/docs/ja/getting-started)
- [Storybook](https://storybook.js.org/docs/guides/guide-react/)