Ⓜ️

Next.js14の環境構築をテンプレート化したい🔫💨[2023]

2023/12/18に公開

今回やること 🧩

成果物 🦋

https://github.com/tara-is-ok/template-next

環境
package.json
{
  "name": "template-next",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "format": "prettier --write",
    "prepare": "husky install"
  },
  "dependencies": {
    "next": "14.0.4",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "@typescript-eslint/eslint-plugin": "^6.14.0",
    "@typescript-eslint/parser": "^6.14.0",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.0.4",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-eslint-comments": "^3.2.0",
    "eslint-plugin-import": "^2.29.1",
    "eslint-plugin-react": "^7.33.2",
    "eslint-plugin-simple-import-sort": "^10.0.0",
    "husky": "^8.0.0",
    "lint-staged": "^15.2.0",
    "postcss": "^8",
    "prettier": "^3.1.1",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

Next.js アプリケーションの作成 💧

Node のバージョンを確認

前提としてNode.js 18.17以降が必要です。

$ node -v
v20.10.0

create-next-app

全て Yes で設定!

npx create-next-app@latest

Need to install the following packages:
create-next-app@14.0.4
Ok to proceed? (y)
✔ What is your project named? … template-next
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … Yes
? What import alias would you like configured? › @/*

最後の? What import alias would you like configured? › @/*については、
ファイルパスの先頭に@/と指定するとプロジェクトディレクトリとなるようエイリアスの設定をしてます 🪽

sample
import { Sample } from '../../../components/sample';
↓ こんな感じ
import { Sample } from '@/components/sample';

確認

すでに最低限ですがセットアップは完了しているため開発サーバーを起動してみる 🖲️

npm run dev
↓
> template-next@0.1.0 dev
> next dev
▲ Next.js 14.0.4
- Local: http://localhost:3000

このようになっていれば 🥳
initialized next app

https://nextjs.org/docs/getting-started/installation

Prettier の設定 🤸🏾‍♂️

インストール

npm install -D prettier

設定を追加

.prettierrc.json
{
    "semi": false,
    "singleQuote": true,
    "trailingComma": "all"
}

scripts も追加しておく

package.json
{
  "scripts": {
     ~~
+    "format": "prettier --write",
  }
}

vscode の拡張を追加

https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode

ESLint🧊

以前書いた記事を参考に作成していく
https://zenn.dev/tara_is_ok/articles/271aebe29f921e

インストール

create-next-app時にeslintの install は終わっているがルール関連のインストールがまだのため追加していきます

npm install -D @typescript-eslint/eslint-plugin eslint-plugin-eslint-comments @typescript-eslint/parser eslint-config-prettier eslint-plugin-import eslint-plugin-react eslint-plugin-simple-import-sort

独自設定するルール一覧

コーディング規約に基づいて以下のルールを追加します 💫
https://zenn.dev/tara_is_ok/articles/05b3a6dc2ebdd7

  • eslint-disable を使う場合はコメント必須!
  • マジックナンバー禁止
  • import/export の sort
  • 型定義は type を使う
  • any 禁止
  • 未使用の変数を残さない
  • 自己閉じタグを使う
    • <Component />
  • boolean の属性表記は省略形
  • 省略可能な object key は省略する
    • {data: data} → {data}
  • 原則 default export 禁止
    • app router ではpage.tsxに当たるものがルーティングされるためルールから除外する
  • アロー関数を使う
  • 命名規則の設定

ルールを追加

記事に合わせるため.eslintrc.json.eslintrc.jsに変更する(深い意味はないです!)

.eslintrc.js
module.exports = {
  extends: [
    'next/core-web-vitals',
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:react/jsx-runtime',
    'plugin:eslint-comments/recommended',
    'prettier',
  ],
  env: { browser: true, node: true, es6: true },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    project: './tsconfig.json',
    tsconfigRootDir: __dirname,
  },
  plugins: ['@typescript-eslint', 'simple-import-sort', 'import'],
  ignorePatterns: ['node_modules/', '.eslintrc.js'],
  rules: {
    'react/jsx-curly-brace-presence': 'warn',
    'simple-import-sort/imports': 'error', //importとexportのソート
    'simple-import-sort/exports': 'error',
    'import/first': 'error',
    'import/newline-after-import': 'error',
    'import/no-duplicates': 'error',
    '@typescript-eslint/consistent-type-definitions': ['warn', 'type'], //型定義はtypeを使う
    '@typescript-eslint/no-explicit-any': 'error', //any禁止
    '@typescript-eslint/no-unused-vars': 'error', //未使用の変数禁止
    'react/self-closing-comp': ['error', { component: true, html: true }], //<Component />のように自己閉タグを使う
    'no-control-regex': 'off', //正規表現中のASCII制御文字ok
    'react/jsx-boolean-value': 'error', //attribute={true} → attribute
    'react/jsx-pascal-case': 'error', //コンポーネント名はパスカルケース
    'object-shorthand': ['warn', 'properties', { avoidQuotes: true }],
    'eslint-comments/require-description': 'error', //eslint-disable-next-lineのコメントは必ず説明を書く。https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/require-description.html
    'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }], //https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/disable-enable-pair.html
    'import/no-default-export': 'error', //default export禁止
    'no-nested-ternary': 'error', //三項演算子のネスト禁止
    'react/function-component-definition': [
      'error', //関数コンポーネントの定義はアロー関数を使う
      { namedComponents: 'arrow-function' },
    ],
    'no-magic-numbers': [
      'error',
      {
        ignore: [-1, 0, 1], //配列検索でindexOf === -1などは許容する
        ignoreDefaultValues: true, //const { tax = 0.1 } = props
        ignoreArrayIndexes: true, //data[100] ok
        enforceConst: true, //マジックナンバーはconstで定義する
      },
    ],
    '@typescript-eslint/naming-convention': [
      'error',
      {
        selector: ['variable', 'method', 'accessor'], //基本的に全てcamelCase
        format: ['camelCase', 'snake_case'],
      },
      {
        selector: ['property'], //APIリクエスト時にPascalCaseとなっている箇所がある
        format: ['camelCase', 'snake_case', 'PascalCase'],
      },
      {
        selector: 'variable', //exportされている定数やコンポーネント
        modifiers: ['exported', 'const'],
        format: ['PascalCase', 'strictCamelCase'],
      },
      {
        selector: 'interface', //interfaceはIをつけない
        format: ['PascalCase'],
        custom: { regex: '^I[A-Z]', match: false },
      },
      { selector: ['class', 'typeAlias', 'enum'], format: ['PascalCase'] },
      {
        selector: ['objectLiteralProperty'], //api requestのheadersの'Content-Type'などが対応するためnullで許容する
        format: null,
        modifiers: ['requiresQuotes'],
      },
    ],
  },
  overrides: [
    {
      files: ['*/app/**/page.tsx', 'layout.tsx', 'tailwind.config.ts'],
      rules: {
        'import/no-default-export': 'off',
        'import/prefer-default-export': 'error',
        '@typescript-eslint/naming-convention': 'off',
      },
    },
  ],
}

vscode の拡張を設定

https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint

Husky + lint-staged の設定 🧃

Husky のインストール

npx husky-init && npm install

https://typicode.github.io/husky/getting-started.html

lint-staged のインストール

npm install -D lint-staged

.husky/pre-commitにコマンドを追加

npx husky set .husky/pre-commit "npx lint-staged"

以下のようになれば ✅

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

ステージングに上がっているファイルのみを対象とする設定を追加

If you would like to use next lint with lint-staged to run the linter on staged git files, you'll have to add the following to the .lintstagedrc.js file in the root of your project in order to specify usage of the --file flag.

Next.js ではnext lintlint-stagedを併用したい場合は以下の設定が必要なため新しく.lintstagedrc.jsを作成していきます

.lintstagedrc.js
const path = require('path')

const buildEslintCommand = (filenames) =>
  `next lint --fix --file ${filenames
    .map((f) => path.relative(process.cwd(), f))
    .join(' --file ')}`

module.exports = {
  '*.{js,jsx,ts,tsx}': [
    () => 'tsc --incremental false --noEmit',
    buildEslintCommand,
    'prettier --write',
  ],
}

公式では ESLint の設定のみの記載のため() => 'tsc --incremental false --noEmit'で型コンパイルエラーのチェックを入れました 🤹‍♂️

https://nextjs.org/docs/pages/building-your-application/configuring/eslint#lint-staged

完成!👩‍🎤

最後に確認をしていきます 🦑

  1. ESLint が効いているか適当にコードを書いてみる
    🤮 コード

    3 つのルール違反をしています

  • 使っていない定数
  • マジックナンバー
  • eslint-disable にコメントを書かない
"use client";
import { FC, useEffect, useState } from "react";

const unused = "unused";

const Sample: FC = () => {
  const [data, setData] = useState<string>("");

  useEffect(() => {
    if (!data) return;
    if (data.length === 1000000) {
      console.log("condition is 1000000");
    }
    setData("data");
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div>
      <p>Sample</p>
    </div>
  );
};

export default Sample;

:::

  1. lint-staged
    実行結果

    ok!🧼

git commit -m "lint-stagedの確認"

✔ Preparing lint-staged...
⚠ Running tasks for staged files...
  ❯ .lintstagedrc.js — 1 file
    ❯ *.{js,jsx,ts,tsx}1 file
      ✔ tsc --incremental false --noEmit
      ✖ next lint --fix --file src/app/sample/page.tsx [FAILED]
      ◼ prettier --write
↓ Skipped because of errors from tasks.
✔ Reverting to original state because of errors...
✔ Cleaning up temporary files...

✖ next lint --fix --file src/app/sample/page.tsx:

./src/app/sample/page.tsx
4:7  Error: 'unused' is assigned a value but never used.  @typescript-eslint/no-unused-vars
11:25  Error: No magic number: 1000000.  no-magic-numbers
Error: Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.  eslint-comments/require-description

info  - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules
husky - pre-commit hook exited with code 1 (error)

:::

ありがとうございました!🌈❤️‍🔥🎅🦞🎈

スクラップはこちら
https://zenn.dev/tara_is_ok/scraps/a11a5dc9872ddb

Discussion