🫐

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