⛰️

Next.js v12.2 + TypeScript + Emotion 環境構築 忘備録

2022/07/16に公開

https://nextjs.org/blog/next-12-2

Next.jsが12.2になってEmotionを利用するのに@emotion/babel-pluginの設定が不要になったりなど、地味に嬉しいアップデートがあったので改めて環境構築忘備録を残したいと思います。

今回利用している主なパッケージとバージョンは下記です。

package version
next 12.2.2
react 18.2.0
typescript 4.7.4
@emotion/react 11.9.3
eslint 8.19.0
stylelint 14.9.1
prettier 2.7.1
husky 8.0.1

作成した環境は、GitHubリポジトリにて公開しています。参考になれば幸いです。
https://github.com/tsuki-lab/nextjs-latest-starter/releases/tag/0.1.0

🏭 Next.js(TypeScript)

yarn create next-app --typescript

上記コマンドを実行してNext.js環境を作成します。
対話式で、プロジェクト名を聞かれるので答えます。

https://nextjs.org/docs#setup

この状態で、もうすでにNext.js環境の作成が完了しました。
表示を確認したい場合は、yarn devを実行してください。

srcフォルダの作成

このコマンドで作成したプロジェクトは、Next.jsの主要ソースがプロジェクトルートに展開されてしまうので、srcフォルダを作成してファイルを移動します。

mkdir src
mv pages src/pages
mv styles src/styles

ailas設定

コンポーネントなどのファイルをimportする際に相対パスではなく、絶対パスで指定できるようにtsconfig.jsonを編集します。

tsconfig.json
  {
    "compilerOptions": {
+     "baseUrl": "./",
+     "paths": {
+       "@/*": [ "./src/*" ],
+       "/*": [ "*" ]
+     },
      "target": "es5",
      "lib": [ "dom", "dom.iterable", "esnext" ],
      "allowJs": true,
      "skipLibCheck": true,
      "strict": true,
      "forceConsistentCasingInFileNames": true,
      "noEmit": true,
      "esModuleInterop": true,
      "module": "esnext",
      "moduleResolution": "node",
      "resolveJsonModule": true,
      "isolatedModules": true,
      "jsx": "preserve",
      "incremental": true
    },
    "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx" ],
    "exclude": [ "node_modules" ]
  }

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

💅 Emotion

Next.jsがEmotionをサポートしてくれたのでEmotionで環境構築をします。

yarn add @emotion/react

上記コマンドでパッケージを追加します。

https://emotion.sh/docs/@emotion/react

css属性を扱えるようにするために、tsconfig.jsonを編集します。

tsconfig.json
  {
    "compilerOptions": {
      "baseUrl": "./",
      "paths": {
        "@/*": [ "./src/*" ],
        "/*": [ "*" ]
      },
      "target": "es5",
      "lib": [ "dom", "dom.iterable", "esnext" ],
      "allowJs": true,
      "skipLibCheck": true,
      "strict": true,
      "forceConsistentCasingInFileNames": true,
      "noEmit": true,
      "esModuleInterop": true,
      "module": "esnext",
      "moduleResolution": "node",
      "resolveJsonModule": true,
      "isolatedModules": true,
      "jsx": "preserve",
+     "jsxImportSource": "@emotion/react",
      "incremental": true
    },
    "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx" ],
    "exclude": [ "node_modules" ]
  }

vscodeを利用しているのであれば、こちらの拡張機能を追加するとスタイリングしやすくなるかと思います。
https://marketplace.visualstudio.com/items?itemName=styled-components.vscode-styled-components

Emotionを利用してsrc/pages/index.tsxをスタイリングしたサンプルを記載しますので参考になればと思います。

スタイリングサンプル
src/pages/index.tsx
import { css } from '@emotion/react'
import type { NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'

const Home: NextPage = () => {
  return (
    <div
      css={css`
        padding: 0 2rem;
      `}
    >
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main
        css={css`
          display: flex;
          min-height: 100vh;
          flex: 1;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          padding: 4rem 0;
        `}
      >
        <h1
          css={css`
            margin: 0;
            font-size: 4rem;
            line-height: 1.15;
            text-align: center;
          `}
        >
          Welcome to{' '}
          <a
            href="https://nextjs.org"
            css={css`
              color: #0070f3;
              text-decoration: none;

              &:hover,
              &:focus,
              &:active {
                text-decoration: underline;
              }
            `}
          >
            Next.js!
          </a>
        </h1>

        <p
          css={css`
            margin: 4rem 0;
            font-size: 1.5rem;
            line-height: 1.5;
            text-align: center;
          `}
        >
          Get started by editing{' '}
          <code
            css={css`
              padding: 0.75rem;
              background: #fafafa;
              border-radius: 5px;
              font-family: Menlo, Monaco, 'Lucida Console', 'Liberation Mono',
                'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New',
                monospace;
              font-size: 1.1rem;
            `}
          >
            pages/index.tsx
          </code>
        </p>

        <div
          css={css`
            display: flex;
            max-width: 800px;
            flex-wrap: wrap;
            align-items: center;
            justify-content: center;

            @media (max-width: 600px) {
              width: 100%;
              flex-direction: column;
            }
          `}
        >
          <Card href="https://nextjs.org/docs">
            <h2>Documentation &rarr;</h2>
            <p>Find in-depth information about Next.js features and API.</p>
          </Card>

          <Card href="https://nextjs.org/learn">
            <h2>Learn &rarr;</h2>
            <p>Learn about Next.js in an interactive course with quizzes!</p>
          </Card>

          <Card href="https://github.com/vercel/next.js/tree/canary/examples">
            <h2>Examples &rarr;</h2>
            <p>Discover and deploy boilerplate example Next.js projects.</p>
          </Card>

          <Card href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app">
            <h2>Deploy &rarr;</h2>
            <p>
              Instantly deploy your Next.js site to a public URL with Vercel.
            </p>
          </Card>
        </div>
      </main>

      <footer
        css={css`
          display: flex;
          flex: 1;
          align-items: center;
          justify-content: center;
          padding: 2rem 0;
          border-top: 1px solid #eaeaea;
        `}
      >
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
          css={css`
            display: flex;
            flex-grow: 1;
            align-items: center;
            justify-content: center;
          `}
        >
          Powered by{' '}
          <span
            css={css`
              height: 1em;
              margin-left: 0.5rem;
            `}
          >
            <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
          </span>
        </a>
      </footer>
    </div>
  )
}

export default Home

export const Card: React.FC<JSX.IntrinsicElements['a']> = ({
  children,
  ...props
}) => (
  <a
    {...props}
    css={css`
      max-width: 300px;
      padding: 1.5rem;
      border: 1px solid #eaeaea;
      margin: 1rem;
      border-radius: 10px;
      color: inherit;
      text-align: left;
      text-decoration: none;
      transition: color 0.15s ease, border-color 0.15s ease;

      &:hover,
      &:focus,
      &:active {
        border-color: #0070f3;
        color: #0070f3;
      }

      h2 {
        margin: 0 0 1rem;
        font-size: 1.5rem;
      }

      p {
        margin: 0;
        font-size: 1.25rem;
        line-height: 1.5;
      }
    `}
  >
    {children}
  </a>
)

📙 Prettier

formatterとしてPrettierを導入します。

yarn add -D prettier

上記コマンドでパッケージをインストールします。

package.jsonのnpm scriptsに下記を追加します。

package.json
  {
    "scripts": {
      "dev": "next dev",
      "build": "next build",
      "start": "next start",
      "lint": "next lint",
+     "prettier": "prettier --config .prettierrc.json --ignore-path .gitignore './**/*.{ts,tsx,json,md}'",
+     "format": "yarn prettier --write"
    }
  }

vscodeでファイルを保存時にPrettierの設定を適応する

今回は、vscodeのワークフロー設定を例に紹介します。.vecode/settings.jsonを作成(編集)します。

.vscode/setting.json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "prettier.configPath": ".prettierrc.json"
}

🔖 ESLint

必要なパッケージと今回設定するプラグインなどをインストールします。

yarn add -D eslint eslint-config-google eslint-config-next eslint-config-prettier @typescript-eslint/eslint-plugin

既存で.eslintrc.jsonが存在すると思いますが、今回は.eslintrc.jsを利用したいので削除します。

rm .eslintrc.json

.eslintrc.jsには下記を追加します。

.eslintrc.js
module.exports = {
  env: {
    es2021: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'next/core-web-vitals',
    'google',
    'prettier',
  ],
  rules: {
    'require-jsdoc': ['off'], // 必要に応じて変更してください。
    'import/order': ['error', { alphabetize: { order: 'asc' } }],
    '@next/next/no-img-element': ['off'],
  },
}

vscodeでファイルを保存時にESLintの設定を適応する

.vecode/settings.jsonを編集します。

.vscode/setting.json
  {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true,
+   "editor.codeActionsOnSave": {
+     "source.fixAll.eslint": true
+   },
    "prettier.configPath": ".prettierrc.json"
  }

🎨 Stylelint

必要なパッケージと今回設定するプラグインなどをインストールします。

yarn add -D stylelint stylelint-config-idiomatic-order stylelint-config-prettier stylelint-config-standard stylelint-prettier @stylelint/postcss-css-in-js

設定ファイルとして.stylelintrc.jsを作成します。

.stylelintrc.js
module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-idiomatic-order',
    'stylelint-prettier/recommended',
  ],
  overrides: [
    {
      files: ['src/**/*.{ts,tsx}'],
      customSyntax: '@stylelint/postcss-css-in-js',
    },
  ],
  rules: {
    'string-quotes': ['single'],
  },
}

package.jsonにnpm scriptsを追加します。

package.json
  {
    "scripts": {
      "dev": "next dev",
      "build": "next build",
      "start": "next start",
      "lint": "next lint",
+     "stylelint": "stylelint --ignore-path .gitignore './src/**/*.{css,scss,ts,tsx}'",
      "prettier": "prettier --config .prettierrc.json --ignore-path .gitignore './**/*.{ts,tsx,json,md}'",
      "format": "yarn prettier --write"
    }
  }

vscodeでファイルを保存時にStylelintの設定を適応する

.vecode/settings.jsonを編集します。

.vscode/setting.json
  {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
-     "source.fixAll.eslint": true
+     "source.fixAll.eslint": true,
+     "source.fixAll.stylelint": true
    },
    "prettier.configPath": ".prettierrc.json"
  }

🛠 typecheck(tsc)

Linterだけでは、Typescriptの型定義が正しいか判定されないのでtscを利用して、チェックできるようにします。
npm scripts にコマンドを追加します。

package.json
  {
    "scripts": {
      "dev": "next dev",
      "build": "next build",
      "start": "next start",
      "lint": "next lint",
      "stylelint": "stylelint --ignore-path .gitignore './src/**/*.{css,scss,ts,tsx}'",
+     "typecheck": "tsc",
      "prettier": "prettier --config .prettierrc.json --ignore-path .gitignore './**/*.{ts,tsx,json,md}'",
      "format": "yarn prettier --write"
    }
  }

🐶 husky

コードをコミットする際に今まで設定してきた、Linterなどを実行して、コード品質を担保するためにhuskyを導入します。

npx husky-init && yarn 

公式で推奨されている方法でインストールします。

https://typicode.github.io/husky/#/?id=automatic-recommended

インストールが完了するとプロジェクトに.huskyフォルダが作成されているかと思います。
その中のpre-commitを編集します。

pre-commit
  #!/bin/sh
  . "$(dirname "$0")/_/husky.sh"

- npm test
+ yarn typecheck
+ yarn lint
+ yarn stylelint
+ yarn prettier -l

♻️ fixコマンドの追加

huskyにより、コミットができなかった場合など、手作業でソースコードを直すのは大変です。
機械的に治せる部分を一括で修正をかけられるように npm scripts を追加します。

package.json
  {
    "scripts": {
      "dev": "next dev",
      "build": "next build",
      "start": "next start",
      "lint": "next lint",
      "stylelint": "stylelint --ignore-path .gitignore './src/**/*.{css,scss,ts,tsx}'",
+     "fix": "yarn lint --fix && yarn stylelint --fix && yarn format",
      "typecheck": "tsc",
      "prettier": "prettier --config .prettierrc.json --ignore-path .gitignore './**/*.{ts,tsx,json,md}'",
      "format": "yarn prettier --write",    
      "prepare": "husky install"
    }
  }

これにより、yarn fixが実行可能になりました。

最後に

この構成をベースにホスティング環境や開発要件によって色々カスタマイズしていけると思います。
かくいう私もここを起点に色々これからリポジトリを組み立てていこうと思います。

Twitterでフロントエンド周りのこと呟いたりしてます。
よければ絡んでくださいー。

Discussion