最近、個人開発などでWebアプリを開発するとなると、Next.js+Vercelで開発することが多いです。
その中でも、TypeScriptやESLintなどの設定が固まりつつあるので、自分の開発しやすい設定を紹介します。
Create a project
create-next-app
でNext.jsを作成する際の設定
$ npx create-next-app@latest
What is your project named? my-app
Would you like to use TypeScript with this project? Yes
Would you like to use ESLint with this project? Yes
Would you like to use Tailwind CSS with this project? No
Would you like to use `src/` directory with this project? Yes
Use App Router (recommended)? Yes
Would you like to customize the default import alias? Yes
TailwindだけNoとしています。
TypeScript、ESLintは当然入れています。
src/
ディレクトリを使うのは、ルートディレクトリが.eslintrc
, .prettierrc
, tsconfig.json
, codegen.config.js
, pathpida.const.js
, ... と設定ファイルで溢れ返るので開発中に触るファイルはsrc/
以下にまとまっていた方が見やすくなるので、src/
を使うようにしています。
App Routerは、今後のスタンダードになっていくと思うので、Yesにしています。
the default import aliasはimport ... from '@/...'
のようにエイリアスで参照できる設定を入れてくれます。自分はエイリアス参照が好みなので入れています。
Tailwindを入れていない理由としては、例えばMUIなど外部コンポーネントライブラリを使う場合などに、ここのスタイルはTailwind、こっちのスタイルはMUIなど、ごちゃごちゃになるので使っていません。
あと、クラス名でコードが埋まってしまい、可読性も落ちるのが好きじゃないので使っていません。
TypeScriptの設定
TypeScriptの設定は、create-next-app
を使って生成されるファイルで満足しているのでほぼいじっていません。
ひとつだけ設定を追加していて、それはnoUncheckedIndexedAccessです
{
"compilerOptions": {
"noUncheckedIndexedAccess": true,
}
}
この設定を追加すると、オブジェクトや配列のプロパティにアクセスする際に、undefinedを考慮した書き方にしないとTypeErrorにしてくれる機能です。
例えば、以下のコードでstr
の型が何になるかというと、str: string | undefined
となります。
const arr: string[] = ['1', '2']
const str = arr[0]
しかし、noUncheckedIndexedAccess
を設定しないと、str: string
となります。
これの何がいけないかというと、arr[3] = undefined
なのに、string
型と認識してしまい。arr[3].length
なんて書いてもエラーも出ずにコンパイルが通ってしまいます。
こういったバグの温床を防ぐために、、noUncheckedIndexedAccess
を設定しています。
ESLint, Prettier
Linterには、ESLint,Prettierを入れています。
Prettier
フォーマットの設定は、以下のようにしています。
module.exports = {
trailingComma: 'all',
semi: false,
singleQuote: true,
}
trailingComma: 'all'
常にカンマを入れる設定をしています。
理由は、gitの差分を見やすくするためです。
例えば以下のある型にプロパティを追加したいとき、設定によって差分が変わります。
デフォルトのes5
の場合
trailingComma: 'all'
の場合
画像のようにカンマが追加される行も差分として表示され、若干見づらくなるので、trailingComma: 'all'
を好んで使っています。
semi: false
行末につける;
を省略しています。
セミコロンつける必要なくない?てことがほとんどなので、省略しています。
singleQuote: true
文字列を書くときは、'hoge'
とシングルクォートで囲います。
これは好みかもしれません。昔からシングルクォートだったので入れています。
ESLint
初期設定では、'next/core-web-vitals'
しか入っておらず、ほぼLintが効いていない状態なのでカスタマイズしています。
以下の設定がどのプロジェクトでも使っている設定です。
/**
* @type {import('eslint').ESLint.ConfigData}
*/
module.exports = {
extends: ['eslint:recommended', 'next/core-web-vitals'],
overrides: [
{
files: ['**/*.ts?(x)'],
extends: ['plugin:@typescript-eslint/recommended'],
rules: {
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
// Note { ref, ...others } = propsのように不要なrefの取り除くときに、unused-vars警告を無視できるが、refを使っているのかがわからなくなってしまうので使わない
// 代わりに、varsIgnorePatternで、{ ref: _, ...others } = propsのように回避できるようにする
// ignoreRestSiblings: true,
varsIgnorePattern: '^_',
},
],
},
},
{ files: ['*'], extends: ['prettier'] },
],
rules: {
'no-duplicate-imports': 'error',
curly: 'error',
eqeqeq: 'error',
'no-nested-ternary': 'error',
'no-param-reassign': 'error',
'no-restricted-imports': [
'error',
{
patterns: ['../*'],
},
],
'no-return-assign': 'error',
'no-return-await': 'error',
'object-shorthand': 'error',
'prefer-const': 'error',
yoda: 'error',
'import/order': [
'error',
{
alphabetize: {
order: 'asc',
},
distinctGroup: false,
pathGroups: [
{
pattern: '@/**',
group: 'parent',
position: 'before',
},
],
},
],
},
}
'eslint:recommended'
, 'plugin:@typescript-eslint/recommended'
で、ESLintとTypeScriptのおすすめのLintを入れています。
rulesには、開発者によって書き方の揺れが発生するのを防いだり、他の人にとって理解しづらいコードにならないようにするためのルールを追加しています。
その中でも工夫しているルールをピックアップして紹介します。
@typescript-eslint/no-unused-vars
no-unused-vars
は、使ってない変数をかくなよというルールですが、少しルールに手を加えています。
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
例えば以下のように使いたいときに、@typescript-eslint/no-unused-vars
が効いているとアラートが出てしまい困ることがありました。
const { ref, ...others } = props
return (
<div>
<Component {...others} />
</div>
)
props
から変数ref
を取り除いて、Componentにprops
を流したときに、ref
は使っていないのでアラートが出てしまいます。
それを解消するために上記の設定をすると以下のように書くことができます。
const { ref: _, ...others } = props
return (
<div>
<Component {...others} />
</div>
)
使っていないref
を_
という変数にしています。
このように書くことで、refは使っていないということを他の開発者に伝えることができ、わざわざこの変数を使っていないか確認する必要がありません。
アンダーバーから始まっている変数は使っていないということが明示できているので。
GitHub Actions
GitHub Actionsを使って、LintやTypeCheckの確認を行うようにしています。
ワークフローのコードは、少し長いので以下のリンク先でご覧ください。
やっていることは、setup-node
ジョブで環境セットアップを行い、並列でESLint,Prettier,TypeScriptビルドのジョブを走らせています。
node_modulesをキャッシュすることで実行時間を削減
actions/setup-node@v3
にキャッシュの機能がありますが、それではnode_modulesをキャッシュすることができず、毎回yarn install
が走ってしまい実行時間がかかってしまいます。
ですので、node_modulesをキャッシュする設定を加えます。それが以下になります。
jobs:
setup-node:
steps:
- uses: actions/cache@v3
id: node_modules_cache_id
with:
path: ${{ github.workspace }}/node_modules
key: ${{ runner.os }}-node${{ env.NODE_BUILD_VERSION }}-yarn-${{ hashFiles('**/yarn.lock') }}
- name: Package install
if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
run: yarn install --frozen-lock
actions/cache@v3
でnode_modulesディレクトリをキャッシュするように設定して、キャッシュが見つかった場合はyarn install
を省略しています。
キャッシュキーに各パラメータを持たせることで、nodeバージョンやyarn.lockに変更があったらキャッシュを更新するようにしています。
キャッシュを使う側のジョブでは、このキャッシュをリストアする設定を追加します。
jobs:
setup-node:
steps:
- name: Restore cache node_modules
id: restore_cache_node_modules
uses: actions/cache/restore@v3
with:
path: ${{ github.workspace }}/node_modules
key: ${{ runner.os }}-node${{ env.NODE_BUILD_VERSION }}-yarn-${{ hashFiles('**/yarn.lock') }}
actions/cache/restore@v3
を使って、先程のキーからキャッシュを取得します。
まとめ
Next.jsで開発する際の設定を、なぜその設定なのか、どう工夫しているかを紹介しました。
プロジェクトによってzustand, zod, storybook, jestなどのツールを導入するので、それに合わせた設定を追加していますが、今回紹介したものはどのプロジェクトにも共通して入れています。
説明したコードは以下においていますので参考にしてみてください。
https://codesandbox.io/p/sandbox/lingering-hill-9w2p9v?f