Closed16

ポートフォリオ作ってみる。JamStack 学習目的で。

ピン留めされたアイテム
志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

はじめに

JamStackを学習する目的で、ブログを作ってみる。

環境

package.json
  "dependencies": {
    "@next/font": "^13.2.4",
    "microcms-js-sdk": "^2.3.2",
    "next": "13.2.4",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "@types/jest": "^29.5.0",
    "@types/node": "18.15.3",
    "@types/react": "18.0.28",
    "@types/react-dom": "18.0.11",
    "@typescript-eslint/eslint-plugin": "^5.55.0",
    "@typescript-eslint/parser": "^5.55.0",
    "eslint": "^8.36.0",
    "eslint-config-next": "13.2.4",
    "eslint-config-prettier": "^8.7.0",
    "eslint-plugin-import": "^2.27.5",
    "eslint-plugin-jsx-a11y": "^6.7.1",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-simple-import-sort": "^10.0.0",
    "husky": "^8.0.0",
    "jest": "^29.5.0",
    "lint-staged": "^13.2.0",
    "prettier": "^2.8.4",
    "sass": "^1.59.3",
    "ts-jest": "^29.0.5",
    "typescript": "5.0.2"
  }

その他

node: 18.15.0
yarn: 1.22.19

hosting は vercel で行う

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

CSS を選定する過程

CSS Modules

理由

  • Next.js はビルトインサポートしているから
    • CSS Modules を推している感じはある(解釈)
  • CSS をファイルとして出力してくれるというのも評価点
  • 描画のために読み込むCSSを最小限にできる(自分がする)
  • CSS Modulesの方が、『ロードタイム・ランタイムともに、パフォーマンス面で有利』なよう

https://twitter.com/ryosuketter/status/1636017628140683265?s=20

デメリットは

  • いちいち命名しなくてはならないところ

参考

https://developer.hatenastaff.com/entry/2022/09/01/093000

https://qiita.com/70ki8suda/items/b95aeb4d4d3cab57a8fe

https://zenn.dev/takepepe/scraps/6668e9fe402666

https://yoshiko-pg.github.io/slides/20180419-scramble/#20

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

GPT-4のサポートを検討

  • 複数のモジュールを渡してドキュメント化
  • テスト作成
  • 新規モジュール開発

参考

  • ESLintやPrettierの設定内容を1つ1つ教えてくれた
志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

環境構築

nextプロジェクトを作成

TypeScript対応のNext.jsアプリケーションのプロジェクトを作成

ターミナル
$ npx create-next-app personal-blog --typescript --no-tailwind --eslint
  • --typescript: TypeScriptプロジェクトとして初期化
  • --no-tailwind: Tailwind CSSの設定なしで初期化
  • --eslint: ESLintの設定の初期化

https://nextjs.org/docs/api-reference/create-next-app

ターミナル
$ npx create-next-app personal-blog --typescript --no-tailwind --eslint
実行結果
Creating a new Next.js app in /personal-blog/personal-blog.

Using yarn.

Installing dependencies:
- react
- react-dom
- next

yarn add v1.22.19
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 16 new dependencies.
info Direct dependencies
├─ next@13.2.4
├─ react-dom@18.2.0
└─ react@18.2.0
info All dependencies
├─ @next/env@13.2.4
├─ @next/swc-darwin-arm64@13.2.4
├─ @swc/helpers@0.4.14
├─ caniuse-lite@1.0.30001466
├─ client-only@0.0.1
├─ js-tokens@4.0.0
├─ nanoid@3.3.4
├─ next@13.2.4
├─ picocolors@1.0.0
├─ postcss@8.4.14
├─ react-dom@18.2.0
├─ react@18.2.0
├─ scheduler@0.23.0
├─ source-map-js@1.0.2
├─ styled-jsx@5.1.1
└─ tslib@2.5.0
✨  Done in 11.24s.

Installing devDependencies:
- eslint
- eslint-config-next
- typescript
- @types/react
- @types/node
- @types/react-dom

yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 185 new dependencies.
info Direct dependencies
├─ @types/node@18.15.3
├─ @types/react-dom@18.0.11
├─ @types/react@18.0.28
├─ eslint-config-next@13.2.4
├─ eslint@8.36.0
└─ typescript@5.0.2
info All dependencies
├─ @babel/runtime@7.21.0
├─ @eslint-community/eslint-utils@4.3.0
├─ @eslint-community/regexpp@4.4.0
├─ @eslint/eslintrc@2.0.1
├─ @eslint/js@8.36.0
├─ @humanwhocodes/config-array@0.11.8
├─ @humanwhocodes/module-importer@1.0.1
├─ @humanwhocodes/object-schema@1.2.1
├─ @next/eslint-plugin-next@13.2.4
├─ @nodelib/fs.scandir@2.1.5
├─ @nodelib/fs.stat@2.0.5
├─ @nodelib/fs.walk@1.2.8
├─ @pkgr/utils@2.3.1
├─ @rushstack/eslint-patch@1.2.0
├─ @types/json5@0.0.29
├─ @types/node@18.15.3
├─ @types/prop-types@15.7.5
├─ @types/react-dom@18.0.11
├─ @types/react@18.0.28
├─ @types/scheduler@0.16.2
├─ @typescript-eslint/parser@5.55.0
├─ @typescript-eslint/scope-manager@5.55.0
├─ @typescript-eslint/typescript-estree@5.55.0
├─ acorn-jsx@5.3.2
├─ acorn@8.8.2
├─ ajv@6.12.6
├─ ansi-regex@5.0.1
├─ ansi-styles@4.3.0
├─ argparse@2.0.1
├─ aria-query@5.1.3
├─ array-buffer-byte-length@1.0.0
├─ array-includes@3.1.6
├─ array-union@2.1.0
├─ array.prototype.flat@1.3.1
├─ array.prototype.tosorted@1.1.1
├─ ast-types-flow@0.0.7
├─ axe-core@4.6.3
├─ axobject-query@3.1.1
├─ balanced-match@1.0.2
├─ brace-expansion@1.1.11
├─ braces@3.0.2
├─ callsites@3.1.0
├─ chalk@4.1.2
├─ color-convert@2.0.1
├─ color-name@1.1.4
├─ concat-map@0.0.1
├─ cross-spawn@7.0.3
├─ csstype@3.1.1
├─ damerau-levenshtein@1.0.8
├─ deep-is@0.1.4
├─ define-lazy-prop@2.0.0
├─ emoji-regex@9.2.2
├─ enhanced-resolve@5.12.0
├─ es-get-iterator@1.1.3
├─ es-set-tostringtag@2.0.1
├─ es-to-primitive@1.2.1
├─ escape-string-regexp@4.0.0
├─ eslint-config-next@13.2.4
├─ eslint-import-resolver-node@0.3.7
├─ eslint-import-resolver-typescript@3.5.3
├─ eslint-module-utils@2.7.4
├─ eslint-plugin-import@2.27.5
├─ eslint-plugin-jsx-a11y@6.7.1
├─ eslint-plugin-react-hooks@4.6.0
├─ eslint-plugin-react@7.32.2
├─ eslint-scope@7.1.1
├─ eslint@8.36.0
├─ esquery@1.5.0
├─ esrecurse@4.3.0
├─ estraverse@5.3.0
├─ fast-deep-equal@3.1.3
├─ fast-glob@3.2.12
├─ fast-json-stable-stringify@2.1.0
├─ fast-levenshtein@2.0.6
├─ fastq@1.15.0
├─ file-entry-cache@6.0.1
├─ fill-range@7.0.1
├─ find-up@5.0.0
├─ flat-cache@3.0.4
├─ flatted@3.2.7
├─ function.prototype.name@1.1.5
├─ get-symbol-description@1.0.0
├─ get-tsconfig@4.4.0
├─ glob-parent@6.0.2
├─ glob@7.1.7
├─ globalthis@1.0.3
├─ globalyzer@0.1.0
├─ globby@13.1.3
├─ globrex@0.1.2
├─ graceful-fs@4.2.11
├─ grapheme-splitter@1.0.4
├─ has-bigints@1.0.2
├─ has-flag@4.0.0
├─ has-proto@1.0.1
├─ import-fresh@3.3.0
├─ imurmurhash@0.1.4
├─ internal-slot@1.0.5
├─ is-bigint@1.0.4
├─ is-boolean-object@1.1.2
├─ is-callable@1.2.7
├─ is-core-module@2.11.0
├─ is-date-object@1.0.5
├─ is-docker@2.2.1
├─ is-extglob@2.1.1
├─ is-map@2.0.2
├─ is-negative-zero@2.0.2
├─ is-number-object@1.0.7
├─ is-number@7.0.0
├─ is-path-inside@3.0.3
├─ is-set@2.0.2
├─ is-symbol@1.0.4
├─ is-weakmap@2.0.1
├─ is-weakref@1.0.2
├─ is-weakset@2.0.2
├─ is-wsl@2.2.0
├─ isexe@2.0.0
├─ js-sdsl@4.3.0
├─ json-schema-traverse@0.4.1
├─ json-stable-stringify-without-jsonify@1.0.1
├─ json5@1.0.2
├─ jsx-ast-utils@3.3.3
├─ language-subtag-registry@0.3.22
├─ language-tags@1.0.5
├─ locate-path@6.0.0
├─ lodash.merge@4.6.2
├─ lru-cache@6.0.0
├─ merge2@1.4.1
├─ micromatch@4.0.5
├─ minimatch@3.1.2
├─ minimist@1.2.8
├─ ms@2.1.2
├─ natural-compare@1.4.0
├─ object-assign@4.1.1
├─ object-inspect@1.12.3
├─ object-is@1.1.5
├─ object.hasown@1.1.2
├─ open@8.4.2
├─ optionator@0.9.1
├─ p-limit@3.1.0
├─ p-locate@5.0.0
├─ parent-module@1.0.1
├─ path-exists@4.0.0
├─ path-key@3.1.1
├─ path-type@4.0.0
├─ picomatch@2.3.1
├─ prop-types@15.8.1
├─ punycode@2.3.0
├─ queue-microtask@1.2.3
├─ react-is@16.13.1
├─ regenerator-runtime@0.13.11
├─ resolve-from@4.0.0
├─ reusify@1.0.4
├─ rimraf@3.0.2
├─ run-parallel@1.2.0
├─ safe-regex-test@1.0.0
├─ shebang-command@2.0.0
├─ shebang-regex@3.0.0
├─ slash@4.0.0
├─ stop-iteration-iterator@1.0.0
├─ string.prototype.matchall@4.0.8
├─ string.prototype.trim@1.2.7
├─ string.prototype.trimend@1.0.6
├─ string.prototype.trimstart@1.0.6
├─ strip-ansi@6.0.1
├─ strip-bom@3.0.0
├─ strip-json-comments@3.1.1
├─ supports-color@7.2.0
├─ synckit@0.8.5
├─ tapable@2.2.1
├─ text-table@0.2.0
├─ tiny-glob@0.2.9
├─ to-regex-range@5.0.1
├─ tsconfig-paths@3.14.2
├─ tsutils@3.21.0
├─ type-check@0.4.0
├─ type-fest@0.20.2
├─ typed-array-length@1.0.4
├─ typescript@5.0.2
├─ unbox-primitive@1.0.2
├─ uri-js@4.4.1
├─ which-collection@1.0.1
├─ which@2.0.2
├─ word-wrap@1.2.3
├─ yallist@4.0.0
└─ yocto-queue@0.1.0
✨  Done in 8.00s.

Success! Created personal-blog at /personal-blog/personal-blog

A new version of `create-next-app` is available!
You can update by running: yarn global add create-next-app

コミット

https://github.com/ryosuketter/personal-blog/commit/d3f3de873d8a5d8050974c18cc1844cabdf4de53

npx create-next-app コマンドに --typescript オプションを指定したので、必要な TypeScript のパッケージがインストールされました。なので以下のコマンドは入力する必要はありません。

ターミナル
$ yarn add --dev typescript @types/react @types/react-dom @types/node

このプロジェクト用のyarnのバージョンを固定

ターミナル
$ yarn set version 1.22.19

コミット
https://github.com/ryosuketter/personal-blog/commit/c0d658c9af807a6d0df30075b21ae39f8aab99c1

stableバージョンに切り替える方法

ターミナル
$ yarn set version stable

生成された.yarnrc にsave-prefix ""の追加(インストールする package のバージョンを固定する)

https://chore-update--yarnpkg.netlify.app/ja/docs/yarnrc

このプロジェクト用のnodeのバージョンを固定

nodeのバージョン管理はnvm使用しています。このプロジェクト用のnodeのバージョンを固定する場合、Node.jsのバージョン番号だけを書いたテキストファイル`.nvmrcという名前でプロジェクトルートに置く。

.nvmrc
18.15.0
ターミナル
$ nvm use
Found './personal-blog/.nvmrc' with version <18.15.0>
Now using node v18.15.0 (npm v9.5.0)

コミット
https://github.com/ryosuketter/personal-blog/commit/de3440c25defcbd3efd242d5972173a4f7bffd5d

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

tsconfig.json

このファイルはTypeScriptの設定ファイルであり、コンパイラの設定やプロジェクトの構成を指定するものです。

tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src",
    "target": "es5",
    "module": "esnext",
    "jsx": "preserve",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "noEmit": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true
  },
  "exclude": ["node_modules", "deployments", ".next", "out"],
  "include": [
    "next-env.d.ts",
    "globals.d.ts",
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.js",
    "test/**/*.ts",
    "test/**/*.tsx"
  ]
}
設定内容の詳細

以下に主なオプションを説明します。

  • "baseUrl": モジュール名の解決時の基準となるディレクトリを指定します。
  • "target": コンパイル後のJavaScriptのバージョンを指定します。
    • ここでは、ES5に設定されています。
  • "module": このオプションは出力されるJavaScriptがどのようにモジュールを読み込むか指定します。
    • ここでは、ESモジュールに設定されています。
  • "jsx": JSX をどのような構成の JavaScript に出力するかの指定( ex. preserve, react, react-jsx )
    • JSX 構文をそのままにしておくか(preserve)
    • React の構文に書き換えるか
    • ちなみに、tsc は TypeScript だけじゃなく JSX もコンパイルできる
    • react を指定すると、たとえば <div /> が React.createElement("div") のように変換される
    • ここでは、"preserve"に設定されています。
  • "strict": 厳密な型チェックを有効にしている
  • "esModuleInterop": CommonJS / AMD / UMD 形式のモジュールを ES Modules と同様に扱えるようにする
  • "skipLibCheck": ライブラリファイルの型チェックをスキップ
    • .d.ts の型宣言ファイルに対する型チェックをスキップする
  • "forceConsistentCasingInFileNames": インポート時にファイルパス記述の大文字・小文字を区別する
  • "lib": コンパイルの際に含める組み込みライブラリを指定する
    • (ex.es5,es2015,esnext,dom)
  • "allowJs": TypeScriptのコンパイル対象にJavaScriptファイルを含めるかどうかを指定します。
  • "noEmit": コンパイル結果を出力しないようにします。
  • "moduleResolution": モジュール解決の方針の指定
  • "resolveJsonModule": JSON ファイルをインポートしてできたオブジェクトに自動的に型をつける
  • "isolatedModules": 各ファイルを個別にコンパイルするための設定です。

excludeとincludeオプションを使用することで、コンパイル対象となるファイルや除外するファイルを指定できます。

コミット
https://github.com/ryosuketter/personal-blog/commit/0006cb4275762b427d8d41a529ffc2cd44b76904

参考
志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

ファイルの整理

不要ファイル削除

ターミナル
$ rm -rf public && rm -rf styles && rm -rf pages/api

コミット
https://github.com/ryosuketter/personal-blog/commit/3e2691cc684f96c97e1b1146be576b6fd18e982f

src/ ディレクトリ作成と移動

ターミナル
$ mkdir src/
ターミナル
$ mv pages src/pages/

コミット
https://github.com/ryosuketter/personal-blog/commit/15cfc1a71a0b195e3af8c194847290e7e458b466

現時点のディレクトリ

他のファイルは省略してます。

ディレクトリ

-├── public
-├── styles
-│── pages
-│  ├── _app.tsx
-│  ├── index.tsx
-│  └── api
-│       └── hello.ts
+├── src
+│   └── pages
+│       ├── _app.tsx
+│       └── index.tsx

_app.tsx の整理

globals.css を削除したので、それをimportしている記述を削除

src/pages/_app.tsx
-  import '../styles/globals.css'
import type { AppProps } from "next/app"

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

export default MyApp

コミット

https://github.com/ryosuketter/personal-blog/commit/4ce51c427b630e71fa9e12546201e032b4abfe72

index.tsx の整理

Home.module.css を削除したので、それをimportしている記述を削除など

コミット
https://github.com/ryosuketter/personal-blog/commit/ce4f6ef05e938fbd5696fc6d9982d088efd6fb6f

pages/index.tsxは色々消します。

変数名もPageに統一しました。

コミット
https://github.com/ryosuketter/personal-blog/commit/40537a7f1a36425d7eb0c1917140822f131dec6f

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

ESLint の環境を作る

JavaScript の静的解析のデファクトスタンダード。
ルールに基づいて構文エラーや不具合になりそうな箇所を警告してくれる。

すでにインストール済みのもの

  • eslint

これからインストールするもの

  • @typescript-eslint/eslint-plugin
    • TypeScript向けのルールを追加するパッケージ
  • @typescript-eslint/parser
    • TypeScriptをESLintが理解できるようにパース(プログラムで扱えるようなデータ構造の集合体に変換)する
    • TypeScriptはJavaScriptの構文を拡張した言語なので、このパーサーさえ入れておけば、TypeScriptに限らずJavaScriptのこのパーサーひとつで、TypeScriptとJavaScriptのファイルどちらもリントできるようになります
  • eslint-plugin-react
    • ESLintのReact用プラグイン
  • eslint-plugin-react-hooks
    • React Hooks を使ったとき、適切な記述になるよう用意されたルールを追加する
  • eslint-plugin-import
    • import の自動ソートを行う
  • eslint-plugin-simple-import-sort
    • import の自動ソートを行う
  • eslint-plugin-jsx-a11y
    • Web アクセシビリティに配慮した記述を JSX で行うための各種ルールを追加する

インストール コマンド

ターミナル
$ yarn add --dev \
  @typescript-eslint/eslint-plugin \
  @typescript-eslint/parser \
  eslint-plugin-react \
  eslint-plugin-react-hooks \
  eslint-plugin-import \
  eslint-plugin-simple-import-sort \
  eslint-plugin-jsx-a11y
出力結果
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 11 new dependencies.
info Direct dependencies
├─ @typescript-eslint/eslint-plugin@5.55.0
├─ @typescript-eslint/parser@5.55.0
├─ eslint-plugin-import@2.27.5
├─ eslint-plugin-react@7.32.2
├─ eslint-plugin-simple-import-sort@10.0.0
├─ eslint-plugin-jsx-a11y@6.7.1
├─ eslint-plugin-react-hooks@4.6.0
└─ eslint@8.36.0
info All dependencies
├─ @types/json-schema@7.0.11
├─ @types/semver@7.3.13
├─ @typescript-eslint/eslint-plugin@5.55.0
├─ @typescript-eslint/parser@5.55.0
├─ @typescript-eslint/type-utils@5.55.0
├─ eslint-plugin-import@2.27.5
├─ eslint-plugin-react@7.32.2
├─ eslint-plugin-simple-import-sort@10.0.0
├─ eslint-plugin-jsx-a11y@6.7.1
├─ eslint-plugin-react-hooks@4.6.0
├─ eslint@8.36.0
└─ natural-compare-lite@1.4.0
✨  Done in 4.61s.

コミット
https://github.com/ryosuketter/personal-blog/commit/7ebf144708f73cc94c769dfdd61049d4ef143f83

インストール後に出たwarningのmemo
warning Pattern ["@typescript-eslint/parser@^5.55.0"] is trying to unpack in the same destination "/Users/shimizuryousuke/Library/Caches/Yarn/v6/npm-@typescript-eslint-parser-5.55.0-8c96a0b6529708ace1dcfa60f5e6aec0f5ed2262-integrity/node_modules/@typescript-eslint/parser" as pattern ["@typescript-eslint/parser@^5.42.0"]. This could result in non-deterministic behavior, skipping.

warning Pattern ["eslint-plugin-react@^7.32.2"] is trying to unpack in the same destination "/Users/shimizuryousuke/Library/Caches/Yarn/v6/npm-eslint-plugin-react-7.32.2-e71f21c7c265ebce01bcbc9d0955170c55571f10-integrity/node_modules/eslint-plugin-react" as pattern ["eslint-plugin-react@^7.31.7"]. This could result in non-deterministic behavior, skipping.

warning Pattern ["eslint-plugin-import@^2.27.5"] is trying to unpack in the same destination "/Users/shimizuryousuke/Library/Caches/Yarn/v6/npm-eslint-plugin-import-2.27.5-876a6d03f52608a3e5bb439c2550588e51dd6c65-integrity/node_modules/eslint-plugin-import" as pattern ["eslint-plugin-import@^2.26.0"]. This could result in non-deterministic behavior, skipping.

warning Pattern ["eslint-plugin-react-hooks@^4.6.0"] is trying to unpack in the same destination "/Users/shimizuryousuke/Library/Caches/Yarn/v6/npm-eslint-plugin-react-hooks-4.6.0-4c3e697ad95b77e93f8646aaa1630c1ba607edd3-integrity/node_modules/eslint-plugin-react-hooks" as pattern ["eslint-plugin-react-hooks@^4.5.0"]. This could result in non-deterministic behavior, skipping.

warning Pattern ["eslint-plugin-jsx-a11y@^6.7.1"] is trying to unpack in the same destination "/Users/shimizuryousuke/Library/Caches/Yarn/v6/npm-eslint-plugin-jsx-a11y-6.7.1-fca5e02d115f48c9a597a6894d5bcec2f7a76976-integrity/node_modules/eslint-plugin-jsx-a11y" as pattern ["eslint-plugin-jsx-a11y@^6.5.1"]. This could result in non-deterministic behavior, skipping.

今は対応を取らない。困った時用に見返すメモ。

.eslintrc.json の設定値

このESLintの設定ファイル

ESLintの設定

rule は必要になった時に、欲しいものを入れてください。

.eslintrc.json
{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint", "simple-import-sort", "import"],
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "next/core-web-vitals",
  ],
  "env": {
    "es6": true,
    "browser": true,
    "jest": true,
    "node": true
  },
  "rules": {
    "react/react-in-jsx-scope": 0,
    "react/display-name": 0,
    "react/prop-types": 0,
    "@typescript-eslint/consistent-type-definitions": 0,
    "@typescript-eslint/explicit-function-return-type": 0,
    "@typescript-eslint/explicit-member-accessibility": 0,
    "@typescript-eslint/indent": 0,
    "@typescript-eslint/member-delimiter-style": 0,
    "@typescript-eslint/no-explicit-any": 0,
    "@typescript-eslint/no-var-requires": 0,
    "@typescript-eslint/no-use-before-define": 0,
    "@typescript-eslint/triple-slash-reference": [
      "error",
      { "types": "always" }
    ],
    "@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }],
    "no-console": [2, { "allow": ["warn", "error"] }],
    "simple-import-sort/imports": "error",
    "simple-import-sort/exports": "error",
    "import/no-unresolved": "off",
    "sort-imports": "off",
    "react/self-closing-comp": [
      "error",
      {
        "component": true,
        "html": true
      }
    ]
  }
}

ちなみに設定の数値はそれぞれ、0="off" 1="warn" 2="error" という意味です。

コミット
https://github.com/ryosuketter/personal-blog/commit/4424d5cb7fbeb195eb9ea4817ee70475ceebe9ba

このESLintの設定ファイルのrules配下の設定内容は以下のような意味を持ちます。

rules配下
  • react/react-in-jsx-scope: 0
    • ReactコンポーネントでJSXを使用する際に、Reactが自動的にインポートされるためです。
    • Next.jsの場合、このルールは必要ありません。
    • この設定では無効化されています。
  • react/display-name: 0
    • Reactのコンポーネント名を表示するためのルールです。
  • react/prop-types: 0
    • コンポーネントのprop typesの宣言を確認するためのルールです。
    • この設定では無効化されています。
  • @typescript-eslint/explicit-function-return-type: 0
    • TypeScriptで明示的な関数の戻り値の型宣言が必要かどうかを判断するルールです。
    • この設定では無効化されています。
  • @typescript-eslint/explicit-member-accessibility: 0
    • TypeScriptでメンバーのアクセシビリティを宣言する必要があるかどうかを判断するルールです。
    • この設定では無効化されています。
  • @typescript-eslint/indent: 0
    • TypeScriptのインデントを判断するルールです。
    • この設定では無効化されています。
  • @typescript-eslint/member-delimiter-style: 0
    • クラスのメンバーの区切り文字を判断するルールです。
    • この設定では無効化されています。
  • @typescript-eslint/no-explicit-any: 0
    • any型を使用する際のルールです。
    • この設定では無効化されています。
  • @typescript-eslint/no-var-requires: 0
    • require文を使用する際のルールです。
    • この設定では無効化されています。
  • @typescript-eslint/no-use-before-define: 0
    • 変数や関数が宣言される前に使用されることを防止するルールです。
    • この設定では無効化されています。
  • @typescript-eslint/triple-slash-reference
    • トリプルスラッシュ・ディレクティブの使用を許可するかどうかを定義するルール。
    • ここでは eslint-config-standard-with-typescriptが一律禁止にしていたのを、type 属性の場合に限り許可するよう に設定。
  • @typescript-eslint/no-unused-vars: [2, {"argsIgnorePattern": "^_"}]
    • 未使用の変数を検出するルールです。argsIgnorePatternオプションにより、変数名が_で始まる場合は無視されます。
  • no-console: [2, {"allow": ["warn", "error"]}]
    • コンソールログの使用を制限するルールです。allowオプションにより、warnやerrorは許可されます。
  • simple-import-sort/imports: "error"
    • import文をアルファベット順にソートするルールです。
  • simple-import-sort/exports: "error"
    • export文をアルファベット順にソートするルールです。
  • "import/no-unresolved": "off"
    • import文で解決できないモジュールを警告しない
  • "sort-imports": "off"
    • import文の順序を整えない
  • "react/self-closing-comp": ["error", { "component": true, "html": true }]
    • 自己閉じタグを要求する
    • componentで、Reactコンポーネントに自己閉じタグが必要かどうかを設定する
    • htmlで、HTML要素に自己閉じタグが必要かどうかを設定する

参考

https://typescriptbook.jp/tutorials/eslint

vscode 使ってたら vscode の設定入れる

.vscode/settings.json
.vscode/settings.json
{
  "editor.tabSize": 2,
  "editor.codeActionsOnSave": {
    "source.fixAll": true
  },
  "css.validate": false
}

この設定は、Visual Studio Codeのエディターでタブ幅を2に設定し、保存時に自動的にコードを修正するように設定しています。また、CSSファイルの構文チェックを無効にしています。具体的には、次のような意味があります。

  • editor.tabSize: タブ幅を2に設定する。
  • editor.codeActionsOnSave: エディターで保存するときに自動的に修正する。
  • source.fixAll: 保存時にコードを自動的に修正する。
  • css.validate: CSSファイルの構文チェックを無効にする。

ここまできたら、一度 ESLint の設定が動いているか検証します。

vscode 使ってたら、vscode上で例えば未使用の変数を定義して、no-unused-vars の指摘が出てくればOKじゃないでしょうか?

また、コマンドでeslintを起動して検証してみましょう。

ターミナル
$ npx eslint 'src/**/*.{js,jsx,ts,tsx}'

チームで開発する場合は必要な vscode 拡張もあれば用意しておとくといいかも

.vscode/extensions.json
.vscode/extensions.json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
  ]
}

この設定ファイルはVisual Studio Codeの拡張機能の推奨設定を指定するためのものです。recommendations プロパティには、使用することが推奨される拡張機能の一覧が記載されています。この例では、 dbaeumer.vscode-eslint と esbenp.prettier-vscode を推奨しています。この設定を使うことで、VS Codeがこれらの拡張機能をユーザーに推奨するようになります。

コミット
https://github.com/ryosuketter/personal-blog/commit/b5f6fb83d5572a7cc48e2c53b48aaa8fe4f1f020

これやると、まとめてインストールすることができます。

https://qiita.com/otsuky/items/f46f5ee9eb11b3a9a4ba

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

Prettier の環境を作る

続いてPrettierです。PrettierとEslintではルールの競合が起きる場合があります(参考)。

図の引用

Prettierのドキュメントにもある通り、eslint-config-prettierを使用することが推奨?言及?されています。

同じような内容ですが、下記にも同じ言及がああります。

理由

そこで、Prettierと競合するEslintのルールを無効化しつつ、Prettierを用いる方法で環境を作っていきましょう。まずはインストール。

必要なパッケージは次の 2 つ

インストール

ターミナル
$ yarn add -D prettier eslint-config-prettier

コミット
https://github.com/ryosuketter/personal-blog/commit/b5f6fb83d5572a7cc48e2c53b48aaa8fe4f1f020

そして.eslintrc.json を次のように書き換えます。

.eslintrc.json
...
"extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "next/core-web-vitals",
    "prettier"
  ],
...

extends のところ、一番最後に書いてください。他のルールと競合するルール設定を上書きするので。

参考

https://github.com/prettier/eslint-config-prettier#installation

.prettierrc.json
{
  "printWidth": 120,
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true,
  "trailingComma": "none",
  "endOfLine": "auto"
}
Prettierの設定内容

以下のような意味があります。

  • printWidth: 行の最大文字数を指定します。この文字数を超えると、改行が挿入されます。
  • tabWidth: タブ文字の幅を指定します。
  • semi: 文の最後にセミコロンを付けるかどうかを指定します。
  • singleQuote: シングルクォート (') を使用するか、ダブルクォート (") を使用するかを指定します。
  • trailingComma: オブジェクトや配列の最後の要素の後ろにカンマを付けるかどうかを指定します。
  • endOfLine: 改行文字の種類を指定します。autoを指定すると、自動的に検出されます。

ここまでできたら、ESLint で設定してあったルールと Prettier で設定したルールが衝突してないか調べます。

ターミナル
$ yarn eslint-config-prettier 'src/**/*.{js,jsx,ts,tsx}'

出力

yarn run v1.22.19
$ /node_modules/.bin/eslint-config-prettier 'src/**/*.{js,jsx,ts,tsx}'
No rules that are unnecessary or conflict with Prettier were found.
✨  Done in 1.23s.

eslint-config-prettier は不要な設定ルールや Prettier と衝突するルールを検出する CLI ツールを同梱しています。今回、それを実行した結果、衝突してないよって出ました。

ちゃんと設定通り動いていることを確認(VScodeのFormat on save 的なやつ)してコミット。

https://github.com/ryosuketter/personal-blog/commit/ded6c79fcc6368973563f6c571c03877a138755d

yarn formatでprettier 実行できる用にしておく。

コミット

https://github.com/ryosuketter/personal-blog/commit/2f8a2bedc86644e8c4c55cdf94e111d83271d192

参考

https://prettier.io/docs/en/integrating-with-linters.html#notes

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

Jest の導入

インストールする

ターミナル
$ yarn add -D jest ts-jest @types/jest
実行結果
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 151 new dependencies.
info Direct dependencies
├─ @types/jest@29.5.0
├─ jest@29.5.0
└─ ts-jest@29.0.5
info All dependencies
├─ @ampproject/remapping@2.2.0
├─ @babel/compat-data@7.21.0
├─ @babel/core@7.21.3
├─ @babel/helper-compilation-targets@7.20.7
├─ @babel/helper-function-name@7.21.0
├─ @babel/helper-hoist-variables@7.18.6
├─ @babel/helper-module-imports@7.18.6
├─ @babel/helper-module-transforms@7.21.2
├─ @babel/helper-simple-access@7.20.2
├─ @babel/helper-string-parser@7.19.4
├─ @babel/helper-validator-identifier@7.19.1
├─ @babel/helper-validator-option@7.21.0
├─ @babel/helpers@7.21.0
├─ @babel/highlight@7.18.6
├─ @babel/parser@7.21.3
├─ @babel/plugin-syntax-async-generators@7.8.4
├─ @babel/plugin-syntax-bigint@7.8.3
├─ @babel/plugin-syntax-class-properties@7.12.13
├─ @babel/plugin-syntax-import-meta@7.10.4
├─ @babel/plugin-syntax-json-strings@7.8.3
├─ @babel/plugin-syntax-jsx@7.18.6
├─ @babel/plugin-syntax-logical-assignment-operators@7.10.4
├─ @babel/plugin-syntax-nullish-coalescing-operator@7.8.3
├─ @babel/plugin-syntax-numeric-separator@7.10.4
├─ @babel/plugin-syntax-object-rest-spread@7.8.3
├─ @babel/plugin-syntax-optional-catch-binding@7.8.3
├─ @babel/plugin-syntax-optional-chaining@7.8.3
├─ @babel/plugin-syntax-top-level-await@7.14.5
├─ @babel/plugin-syntax-typescript@7.20.0
├─ @babel/template@7.20.7
├─ @babel/traverse@7.21.3
├─ @bcoe/v8-coverage@0.2.3
├─ @istanbuljs/load-nyc-config@1.1.0
├─ @jest/globals@29.5.0
├─ @jest/reporters@29.5.0
├─ @jest/source-map@29.4.3
├─ @jest/test-sequencer@29.5.0
├─ @jridgewell/gen-mapping@0.3.2
├─ @jridgewell/resolve-uri@3.1.0
├─ @jridgewell/set-array@1.1.2
├─ @sinclair/typebox@0.25.24
├─ @sinonjs/commons@2.0.0
├─ @sinonjs/fake-timers@10.0.2
├─ @types/babel__generator@7.6.4
├─ @types/babel__template@7.4.1
├─ @types/graceful-fs@4.1.6
├─ @types/istanbul-lib-coverage@2.0.4
├─ @types/istanbul-lib-report@3.0.0
├─ @types/istanbul-reports@3.0.1
├─ @types/jest@29.5.0
├─ @types/prettier@2.7.2
├─ @types/stack-utils@2.0.1
├─ @types/yargs-parser@21.0.0
├─ @types/yargs@17.0.22
├─ anymatch@3.1.3
├─ babel-jest@29.5.0
├─ babel-plugin-jest-hoist@29.5.0
├─ babel-preset-jest@29.5.0
├─ browserslist@4.21.5
├─ bs-logger@0.2.6
├─ bser@2.1.1
├─ buffer-from@1.1.2
├─ camelcase@6.3.0
├─ char-regex@1.0.2
├─ cjs-module-lexer@1.2.2
├─ cliui@8.0.1
├─ co@4.6.0
├─ convert-source-map@1.9.0
├─ dedent@0.7.0
├─ deepmerge@4.3.1
├─ detect-newline@3.1.0
├─ diff-sequences@29.4.3
├─ electron-to-chromium@1.4.333
├─ error-ex@1.3.2
├─ esprima@4.0.1
├─ execa@5.1.1
├─ expect@29.5.0
├─ fb-watchman@2.0.2
├─ find-up@4.1.0
├─ fsevents@2.3.2
├─ gensync@1.0.0-beta.2
├─ get-caller-file@2.0.5
├─ get-package-type@0.1.0
├─ get-stream@6.0.1
├─ html-escaper@2.0.2
├─ human-signals@2.1.0
├─ is-arrayish@0.2.1
├─ is-fullwidth-code-point@3.0.0
├─ is-generator-fn@2.1.0
├─ is-stream@2.0.1
├─ istanbul-lib-instrument@5.2.1
├─ istanbul-lib-source-maps@4.0.1
├─ istanbul-reports@3.1.5
├─ jest-changed-files@29.5.0
├─ jest-circus@29.5.0
├─ jest-cli@29.5.0
├─ jest-docblock@29.4.3
├─ jest-each@29.5.0
├─ jest-leak-detector@29.5.0
├─ jest-pnp-resolver@1.2.3
├─ jest-resolve-dependencies@29.5.0
├─ jest-util@29.5.0
├─ jest@29.5.0
├─ jsesc@2.5.2
├─ json-parse-even-better-errors@2.3.1
├─ json5@2.2.3
├─ kleur@3.0.3
├─ leven@3.1.0
├─ lines-and-columns@1.2.4
├─ locate-path@5.0.0
├─ lodash.memoize@4.1.2
├─ make-dir@3.1.0
├─ make-error@1.3.6
├─ makeerror@1.0.12
├─ mimic-fn@2.1.0
├─ node-int64@0.4.0
├─ node-releases@2.0.10
├─ normalize-path@3.0.0
├─ npm-run-path@4.0.1
├─ onetime@5.1.2
├─ p-locate@4.1.0
├─ p-try@2.2.0
├─ parse-json@5.2.0
├─ pirates@4.0.5
├─ pkg-dir@4.2.0
├─ pretty-format@29.5.0
├─ prompts@2.4.2
├─ pure-rand@6.0.1
├─ require-directory@2.1.1
├─ resolve-cwd@3.0.0
├─ resolve.exports@2.0.1
├─ signal-exit@3.0.7
├─ sisteransi@1.0.5
├─ source-map-support@0.5.13
├─ source-map@0.6.1
├─ sprintf-js@1.0.3
├─ string-width@4.2.3
├─ strip-final-newline@2.0.0
├─ test-exclude@6.0.0
├─ tmpl@1.0.5
├─ to-fast-properties@2.0.0
├─ ts-jest@29.0.5
├─ type-detect@4.0.8
├─ update-browserslist-db@1.0.10
├─ v8-to-istanbul@9.1.0
├─ walker@1.0.8
├─ wrap-ansi@7.0.0
├─ write-file-atomic@4.0.2
├─ y18n@5.0.8
├─ yargs-parser@21.1.1
└─ yargs@17.7.1
✨  Done in 12.04s.

コミット
https://github.com/ryosuketter/personal-blog/commit/d46260c202c92e2d18967ece9e979973e6c69161

設定ファイルを作成

jest.config.js
// jest.config.js
const nextJest = require('next/jest')

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './',
})

// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const customJestConfig = {
  // Add more setup options before each test is run
  // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
  moduleDirectories: ['node_modules', '<rootDir>/'],
  // ↓1行を変更しています: React Testing Libraryはまだ利用しないのでコメントアウト
  // testEnvironment: 'jest-environment-jsdom',
}

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)

上記のコードは、Next.jsでテストを実行するためのJestの設定ファイルです。

以下は主なポイントです。

  • next-jestモジュールを使用して、Next.jsアプリケーションの next.config.js や .env ファイルを読み込むための設定を行っています。
  • customJestConfigには、Jestに渡す追加の設定を記述します。
  • moduleDirectoriesは、import文で使用されるモジュールの探索パスを設定しています。
  • ここでは node_modules とルートディレクトリ <rootDir>/ を指定しています。
  • testEnvironmentはコメントアウトされていて、React Testing Libraryを使用しないために今は無効しています

コミット

https://github.com/ryosuketter/personal-blog/commit/8a0e77935754860b09787f892c0099e2901ae251

テスト実行用のnpmスクリプトを追加

package.json
  "scripts": {
    ...
    "test": "jest --watch",
    "test:ci": "jest",
    ...
  }

コミット
https://github.com/ryosuketter/personal-blog/commit/dc6117e9e52e342ed6d225c059de1f9167e50f3f

テストコードを書く

足し算の処理とそのテストコードをサンプルとして書きます。

詳細
src/libs/add.ts
export const add = (a: number, b: number): number => a + b
src/libs/add.test.ts
import { add } from './add'

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3)
  expect(add(10, -5)).toBe(5)
  expect(add(0.1, 0.2)).toBeCloseTo(0.3, 5) // 小数の場合はtoBeCloseToを使う
})

テストを実行する

ターミナル
$ yarn test
$ yarn test:ci

ちゃんと動いているか確認できた。

VSCodeの拡張機能を使って検証してもいいでしょう。自分は下記を使っています。

https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest

コミット
https://github.com/ryosuketter/personal-blog/commit/263dbeabb125dd0b5e0582e1e1cb8cd149392c86

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

husky と lint-staged を入れる

以下の機能が動くように環境を作っていきます。具体的には

  • git のコミット前にコマンドを実行してくれるhusky
  • huskyにはコミットするファイルに対して lint かけてくれるlint-staged

の設定を書きます。

ターミナル
$ yarn add -D husky lint-staged
結果
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 27 new dependencies.
info Direct dependencies
├─ husky@8.0.3
└─ lint-staged@13.2.0
info All dependencies
├─ aggregate-error@3.1.0
├─ clean-stack@2.2.0
├─ cli-cursor@3.1.0
├─ cli-truncate@3.1.0
├─ colorette@2.0.19
├─ commander@10.0.0
├─ eastasianwidth@0.2.0
├─ execa@7.1.1
├─ human-signals@4.3.0
├─ husky@8.0.3
├─ indent-string@4.0.0
├─ is-stream@3.0.0
├─ lilconfig@2.1.0
├─ lint-staged@13.2.0
├─ listr2@5.0.8
├─ log-update@4.0.0
├─ npm-run-path@5.1.0
├─ p-map@4.0.0
├─ pidtree@0.6.0
├─ restore-cursor@3.1.0
├─ rfdc@1.3.0
├─ rxjs@7.8.0
├─ slice-ansi@5.0.0
├─ string-argv@0.3.1
├─ strip-final-newline@3.0.0
├─ through@2.3.8
└─ yaml@2.2.1
✨  Done in 4.77s.

コミット
https://github.com/ryosuketter/personal-blog/commit/128327b8d7566d41c189773ccf867a61b868a07b

husky の 初期化と lint-staged の設定

npx husky-init は、プロジェクトで Git Hooks を管理するためのフレームワークである Husky をセットアップするためのコマンドです。これで、Git Hooks 用の準備が整います。

ターミナル
$ npx husky-init

.husky ディレクトリが作成され、pre-commit フックを有効にするためのスクリプトが追加されます。これにより、コミット時に自動的に実行されるスクリプトを追加できます。具体的には package.jsonのscripts内に "prepare": "husky install" が追加されていることが確認できるはずです。

結果
ターミナル
Need to install the following packages:
  husky-init@8.0.0
Ok to proceed? (y) y
husky-init updating package.json
  setting prepare script to command "husky install"
husky - Git hooks installed
husky - created .husky/pre-commit

please review changes in package.json

lint-staged の設定を package.json に追加します。

package.json
...
  "lint-staged": {
    "*.@(ts|tsx)": [
      "yarn lint",
      "yarn format"
    ]
  },
...

でしたが、以下に変更。

package.json
...
  "lint-staged": {
    "*.@(ts|tsx)": [
      "npx eslint .",
      "yarn format"
    ]
  },
...

コミット

https://github.com/ryosuketter/personal-blog/commit/73811c8cc4b0372b55dec56d286dbd0c80717116

変更した理由

起こった問題

下記の問題に近い

https://github.com/vercel/next.js/issues/27997

私の場合は以下の問題として現れました。

グローバルに適応されるスタイルシートにスタイルを記述し、_app.tsxからimportした際、lint-staged(コミット前をgithook)からリンター(Eslint)を動かす際に以下のエラーが出てコミットできなかった。


✖ yarn lint:
error - Failed to load env from .env.production.local Error: ENOTDIR: not a directory, stat '/Users/shimizuryousuke/dev/personal-blog/src/pages/_app.tsx/.env.production.local'
    at Object.statSync (node:fs:1615:3)
    at Module.loadEnvConfig (/Users/shimizuryousuke/dev/personal-blog/node_modules/@next/env/dist/index.js:1:4657)
    at Object.loadConfig [as default] (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/server/config.js:48:21)
    at nextLint (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/cli/next-lint.js:178:50)
    at /Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/bin/next:141:44 {
  errno: -20,
  syscall: 'stat',
  code: 'ENOTDIR',
  path: '/Users/shimizuryousuke/dev/personal-blog/src/pages/_app.tsx/.env.production.local'
}
error - Failed to load env from .env.local Error: ENOTDIR: not a directory, stat '/Users/shimizuryousuke/dev/personal-blog/src/pages/_app.tsx/.env.local'
    at Object.statSync (node:fs:1615:3)
    at Module.loadEnvConfig (/Users/shimizuryousuke/dev/personal-blog/node_modules/@next/env/dist/index.js:1:4657)
    at Object.loadConfig [as default] (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/server/config.js:48:21)
    at nextLint (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/cli/next-lint.js:178:50)
    at /Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/bin/next:141:44 {
  errno: -20,
  syscall: 'stat',
  code: 'ENOTDIR',
  path: '/Users/shimizuryousuke/dev/personal-blog/src/pages/_app.tsx/.env.local'
}
error - Failed to load env from .env.production Error: ENOTDIR: not a directory, stat '/Users/shimizuryousuke/dev/personal-blog/src/pages/_app.tsx/.env.production'
    at Object.statSync (node:fs:1615:3)
    at Module.loadEnvConfig (/Users/shimizuryousuke/dev/personal-blog/node_modules/@next/env/dist/index.js:1:4657)
    at Object.loadConfig [as default] (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/server/config.js:48:21)
    at nextLint (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/cli/next-lint.js:178:50)
    at /Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/bin/next:141:44 {
  errno: -20,
  syscall: 'stat',
  code: 'ENOTDIR',
  path: '/Users/shimizuryousuke/dev/personal-blog/src/pages/_app.tsx/.env.production'
}
error - Failed to load env from .env Error: ENOTDIR: not a directory, stat '/Users/shimizuryousuke/dev/personal-blog/src/pages/_app.tsx/.env'
    at Object.statSync (node:fs:1615:3)
    at Module.loadEnvConfig (/Users/shimizuryousuke/dev/personal-blog/node_modules/@next/env/dist/index.js:1:4657)
    at Object.loadConfig [as default] (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/server/config.js:48:21)
    at nextLint (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/cli/next-lint.js:178:50)
    at /Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/bin/next:141:44 {
  errno: -20,
  syscall: 'stat',
  code: 'ENOTDIR',
  path: '/Users/shimizuryousuke/dev/personal-blog/src/pages/_app.tsx/.env'
}
/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/lib/find-pages-dir.js:86
            throw new Error("> Couldn't find a `pages` directory. Please create one under the project root");
                  ^

Error: > Couldn't find a `pages` directory. Please create one under the project root
    at Object.findPagesDir (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/lib/find-pages-dir.js:86:19)
    at nextLint (/Users/shimizuryousuke/dev/personal-blog/node_modules/next/dist/cli/next-lint.js:201:55)

この問題の対応策は、.lintstagedrc.jsを導入するか、package.jsonを書き換えるかで、私は後者を選択しました。(原因はよくわかっていません。)

https://github.com/vercel/next.js/issues/27997#issuecomment-900554790

next lintを使わず、直接 eslintを立ち上げるように変更しました。

next lintとは、ESLint によるコードの静的解析を実行する next.js のコマンド

package.json
...
  "lint-staged": {
    "*.@(ts|tsx)": [
      // 変更前
      "yarn lint",
      // 変更後
      "npx eslint ."
      // 変更後(はこれでもいいかも 要検討)
      "npx eslint . --ext .tsx --ext .ts"
      "yarn format"
    ]
  },
...

--ext オプションで拡張子を指定することでLintの対象をにしています。globでやるとひとつもファイルがないとき(ボイラープレート作成直後とか)にエラーになってしまうので、--extオプションにするならした方がいいでしょう。

https://github.com/eslint/eslint/blob/v7.0.0/docs/user-guide/command-line-interface.md#--ext

が、今回は--extで対象を絞らないようにしました。

理由(暗黙的に lint 対象を制限することの是非)

本当は このファイルも lint されて欲しいのに何故かされなくて混乱する、もしくは気づけなかった、みたいなことが起きそうだから。

なので、Lintから除外するファイルは .eslintignore に書きつつ、eslint .でカレントディレクトリ配下のファイル全てを lint 対象にするアプローチにしました。

どのディレクトリが除外されるか明示的になりますし、vscode-eslint が .eslintignore を見て、どのファイルが lint 対象外か認識してくれるので、エディタ上/CLI上での警告に差がなくなるので。

参考

https://www.mizdra.net/entry/2023/01/13/013855

結果

想定通りコミット前にLintが今まで通り効いていることを確認

git のコミット前にコマンドを実行してくれるhuskyの設定を追加

husky の設定の詳細

pre-commit の設定

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

yarn lint-staged

コミット
https://github.com/ryosuketter/personal-blog/commit/3f768b2b7d0492ad466d4159dbcebaf764e349c0

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

CSS Modules の 導入

Next.jsにCSS Modulesを導入します。Next.js は CSS Modulesをビルトインサポートしてるので、大きな設定は不要です。

公式を参考にSassをインストール。

ターミナル
$ yarn add -D sass

コミット
https://github.com/ryosuketter/personal-blog/commit/f3e263405a18fea1b59262bfe2a6b8e261127cdd

ちゃんとスタイルが適応されるか確認
src/pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'

import styles from './index.module.scss'

const Page: NextPage = () => {
  return (
    <div>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
      </Head>
      <main>
        <h1 className={styles.h1}>Hello!</h1>
      </main>
    </div>
  )
}

export default Page
src/pages/index.module.scss
.h1 {
  color: red;
}

されてる。

[方針] コンポーネントのスタイルはどこに書くか?

componentsディレクトリと同じ階層に置く

components/Button.tsxのスタイルはcomponents/Button.module.scssに書く方針。

[方針] stylesディレクトリ内の構造

styles/ディレクトリの方針

styles/global.scss

  • ここにグローバルに(アプリ全体で)読み込みたいスタイルを書く
  • _app.tsximport styles/global.scssするだけで読み込み設定は完了
  • 公式ドキュメント曰く、Normalize.cssはGlobal Stylesheetの例としている
    • [注意] ただ、_app.jsはどのページでも必ず実行されるため注意が必要
    • Global Stylesheetが巨大化すればその分だけ初期ロードは重たくなるだろう

コミット
https://github.com/ryosuketter/personal-blog/commit/e01fc2ddaebd6b2cf9c5d0d06aa1d719a974f412

styles/layouts
レイアウト用のコンポーネントはlayoutsディレクトリに入れる

styles/variables.scss
使いまわしたい変数をここに入れる

_app.tsxとは

Next.js は pages/_app.tsx を特殊なファイルとして扱い、ここで default export しているコンポーネントを、すべてのページコンポーネントの親コンポーネントとして扱います。 具体的には、上記コード中の Component 部分が各ページのコンポーネントに置き換えられた形で動作します。 結果的に、ここでインポートした CSS が、全ページから参照できることになります。 ちなみに、ここで複数の CSS ファイルをインポートしている場合は、Next.js が Web サイトのビルド時に 1 つの CSS ファイルにマージしてくれます。
https://maku.blog/p/s9iry9g/

参考

https://nextjs.org/docs/basic-features/built-in-css-support

https://zenn.dev/catnose99/scraps/5e3d51d75113d3

https://mamehiko.booth.pm/items/1033385

https://zenn.dev/takepepe/articles/css-order-should-matter

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

CSS Modulesでの変数の管理

CSS変数をグローバルに読み込むSCSSに設定すればOKです。

CSS変数には、主に再利用可能な値を格納するために使用されます。たとえば、カラーコード、フォントサイズ、背景色、z-indexなどの値を保存することができます。これらの変数は、Webページ内の複数の場所で同じ値を使用する場合に特に便利です。変数を使用すると、値を一元的に変更することができ、保守性が向上します。例えば、ページの色を変更したい場合、変数に保存された値を変更するだけで、Webページのすべての場所で新しい色が適用されます。

styles/global.scss
:root {
  --navy: #000082;
  --yellow: #f0b400;
}

そして、各module.scssからこれを使って、コンポーネントで読み込みができているか検証してみてください。

Button.module.scss
.section {
  background-color: var(--navy);
}

コミット
https://github.com/ryosuketter/personal-blog/commit/e9d3fc357351cc1fcefd79654c51a626c7b7d076

https://developer.mozilla.org/ja/docs/Web/CSS/Using_CSS_custom_properties

メディアクエリ

コミット
https://github.com/ryosuketter/personal-blog/commit/eb00b102933a87bc6b80e2fb2701cd775e595b3b

参考
https://zenn.dev/link/comments/f365d0208224bc

今回のCSSをためにしに書いてみた

https://github.com/ryosuketter/personal-blog/commit/20b8fd061803e1489389ed33e8314f9ac1dde06c

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

Next13で盛り込まれた新機能、@next/fonts を導入

GoogleFontsをセルフホスティングして、パフォーマンスを向上させているらしい。

この機能によりブラウザーは GoogleFonts のサーバーから Web フォントをダウンロードするためにアクティブな接続を維持する必要がなくなったみたい。

https://nextjs.org/docs/basic-features/font-optimization

導入

$ yarn add @next/font
pages/_app.tsx
import '../styles/global.scss'

import { Rubik } from '@next/font/google'
import type { AppProps } from 'next/app'

import { Layout } from '@/components/Layout'

export const rubik = Rubik({
  preload: false
})

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <div>
      <style jsx global>{`
        html,
        body {
          font-family: ${rubik.style.fontFamily}, 'Hiragino Sans', sans-serif;
        }
      `}</style>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </div>
  )
}

export default MyApp

コミット

https://github.com/ryosuketter/personal-blog/commit/a4098da76889e5ee960f684beda80a5fd0cda6d6

Font 関数の argumentsのうち、subsets だけ指定しました。
なぜなら、デフォルトでは preload が true となっており、その場合は subsetsの指定が無いと警告が出たので。

https://nextjs.org/docs/messages/google-fonts-missing-subsets

subsetsは Google Fonts API のフォントのサブセットを指定するオプションで、フォントの使用言語に基づいて、特定の文字セットを抽出することができます。

詳しくは

https://nextjs.org/docs/api-reference/next/font#subsets

下記のコードを見るとわかりますが、Rubik フォントの文字セットのリストが示されれいます。

リスト内の各要素は文字セットを表す文字列として表現されます。

具体的には、次の文字セットが指定されています。

  • 'cyrillic': キリル文字セット(ロシア語、ブルガリア語など)
  • 'cyrillic-ext': 拡張キリル文字セット
  • 'hebrew': ヘブライ文字セット
  • 'latin': ラテン文字セット
  • 'latin-ext': 拡張ラテン文字セット

日本語はないですね。

これらのサブセットを必要最小限に抑えることで、フォントの読み込み速度を向上させ、ページのパフォーマンスを改善が期待できます。

ちなみに、型定義がしっかりしており、Rubik の場合は以下のような感じです。

何が指定可能なのか、分かりやすかったです。

node_modules/@next/font/dist/google/index.d.ts
export declare function Rubik<T extends CssVariable | undefined = undefined>(options?: {
    weight?: '300' | '400' | '500' | '600' | '700' | '800' | '900' | 'variable' | Array<'300' | '400' | '500' | '600' | '700' | '800' | '900'>;
    style?: 'normal' | 'italic' | Array<'normal' | 'italic'>;
    display?: Display;
    variable?: T;
    preload?: boolean;
    fallback?: string[];
    adjustFontFallback?: boolean;
    subsets?: Array<'cyrillic' | 'cyrillic-ext' | 'hebrew' | 'latin' | 'latin-ext'>;
}): T extends undefined ? NextFont : NextFontWithVariable;

また、変形フォントを読み込む場合は、フォントのウェイトを指定する必要はないです。
(variable fontsでないフォントファミリーは、必ずこのweightを指定する必要があります)

公式は variable fontsを推奨しています。

他にも必要なプロパティは公式を見てください。

https://nextjs.org/docs/basic-features/font-optimization#google-fonts

参考
志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

microCMS

microCMSのアカウントを下記URLから作成する

サービスを作成する

環境変数の設定

micro CMS JavaScript SDK をインストール

$ yarn add microcms-js-sdk

API を Next.js で実行する

$ touch .env.local

microCMSの自分のアカウントからコピーした API_KEY をペーストする

参考

https://document.microcms.io/tutorial/javascript/javascript-getting-started

https://zenn.dev/elletech/articles/nextjs_microcms

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

microCMS

microCMSのアカウントを下記URLから作成する

サービスを作成する

環境変数の設定

micro CMS JavaScript SDK をインストール

$ yarn add microcms-js-sdk

API を Next.js で実行する

$ touch .env.local

microCMSの自分のアカウントからコピーした API_KEY やserviceDomainを.env.localペースト

インストールしたmicrocms-js-sdkからAPIをコールする関数を作成

作成したAPIを呼び出しコンテンツを取得する

コミット

https://github.com/ryosuketter/personal-blog/commit/3c858c06af834786f962867263a2c9808d934296

参考

https://document.microcms.io/tutorial/javascript/javascript-getting-started

https://zenn.dev/elletech/articles/nextjs_microcms

志水 亮介 (Ryosuke Shimizu)志水 亮介 (Ryosuke Shimizu)

Vercelのホスティングは多くの公式を見ればすぐできるのでここには書きません。

超簡素ですが一旦できたので。このスクラップはここでクローズします。

https://personal-blog-rho-seven.vercel.app

今後の予定

  • storybookの導入
    • 導入に意味のあるコンポーネントが作れたら
  • ディレクトリ構成の決定(現状いい加減なので)
  • github action で Eslint + Prettier + Test 実行
    • パフォーマンスの自動計測
このスクラップは2023/03/24にクローズされました