Nuxt.js(v3)でgenerate納品する前にやっておきたい設定

# インストール前に...

各種確認を実施。

##### リリース情報

- [node.jsのリリースノート](https://nodejs.org/en/about/previous-releases/)
- [nuxtのリリースノート](https://github.com/nuxt/nuxt/releases)
- [nuxi(nuxt/cli)のリリースノート](https://github.com/nuxt/cli/releases)
- [vscodeのリリースノート](https://code.visualstudio.com/updates)

##### 動作確認バージョン

- Node.js(v20.11.0)
- nuxt(3.9.3)
- nuxi(nuxt/cli)(v3.10.0)
- vscode(v1.85.1)

##### 更新履歴

- 20240122 初回リリース
- 20240124 fix:余分なファイルをgenerateから避けるコードを多階層対応へ修正
- 20240125 fix:lint関連の設定を細かく調整
- 20240125 fix:eslintの好み設定を追加

##### Node.jsのバージョン

インストール前にNode.jsのバージョンを確認します。最低限必要なノードのバージョンは`v18.0.0`以上です。息の長い開発をするならば、[LTS（推奨版）の最新](https://nodejs.org/en/about/previous-releases/)をお勧めします。

```
% node -v
```

# Nuxt.jsインストール

[公式ドキュメント](https://ja.nuxtjs.org/guide/installation/)を参考に、nuxtをインストールします。

```
% npx nuxi@latest init <project-name>
```

Tips：nuxiは、インストール先に指定できるディレクトリが「空のディレクトリのみ」です。~~既存上書き事件が頻発したからかな。~~ すでに何かしらのファイルが存在する場合は、インストール後、生成されたファイルを移動してください。

途中、利用するパッケージマネージャーについて質問があります。本項では、利用頻度が高そうな`yarn`を選びます。

```
❯ Which package manager would you like to use?
○ npm
○ pnpm
● yarn
○ bun
```

デベロッパーモードで起動して、動作確認します。

```
% cd <project-name>
% yarn run dev
```

Tips：構築先のディレクトリを変更したい場合は、このタイミングで移動しましょう。

# 作業開始前の設定

作業開始前に実施しておくべき設定をまとめます。後からでも変更は可能ですが、納品前に心を折られないように備えておきます。


## editorconfigの設定

ここから、ファイルを生成していくことになるので、エディターの設定をしておきます。
[EditorConfig](http://editorconfig.org/)は、異なるエディタ・IDE間でも一貫したコーディングスタイルを定義、維持するツールです。

利用しているエディタにEditorconfigの機能拡張をインストールして定義ファイルを設置していきます。

- 例：[EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig)

```
% touch .editorconfig
```

```
// .editorconfig

# EditorConfig: http://editorconfig.org/
# 各種エディタに以下をインストールしてください。
# 例：EditorConfig for VS Code https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig

# プロジェクトで以下の設定を利用するか指定します。※falseの場合は、プロジェクトから遡って見つかったファイルを利用します。
root = true

# 以下はファイルごとに改行をつけたUnixスタイルの設定です。
[*]
end_of_line = lf
trim_trailing_whitespace = true

# 基本エンコードはutf-8
charset = utf-8

# タブはtab利用、サイズは2。
indent_style = space
indent_size = 2

# その他、拡張子ごとに指定することができます。
# Markdownファイルの設定。
[*.md]
trim_trailing_whitespace = false
```

## ディレクトリ関連

いつでも変更可能です。

ディレクトリ構造が複雑化する前に、Nuxt.jsのメインディレクトリ`public` `server` `app.vue`を「src」ディレクトリにまとめます。

Tips:いつでも変更可能ですが、作業が進むとディレクトリ構造が複雑化し、間違えの元になります。また、開発が進むと色々と手遅れになる場合があります。

#### ディレクトリの移動

```
% mkdir src
% mv public server app.vue src/
```

#### srcDirの設定

``` typescript
// nuxt.config.ts

export default defineNuxtConfig({
  ...
  srcDir: 'src/',
  ...
})
```

## 納品先ディレクトリを変更できるように設定

いつでも変更可能です。

作業後に格納先ディレクトリを指定された場合に備え、apiプロパティのbaseURLをnodeの環境変数(envファイル)で変更できるようにします。

Tips：`.env`修正後は、nuxtを立ち上げ直しましょう。

```
% touch .env
```

```
# .env

# ディレクトリ構造に合わせる
# NUXT_APP_BASE_URL=/my-dir
```

``` typescript
// nuxt.config.ts

export default defineNuxtConfig({
  ...
  app: {
    // ベースのディレクトリ
    baseURL: process.env.NUXT_APP_BASE_URL,
	...
  },
  ...
})
```

#### 本番環境・ステージング環境・開発環境で分けたい

nuxt3は`dotenv`が入っているためカスタムファイル（`.env.local`）が利用できます。各種envファイルを作成し、`package.json`へコマンドを登録しておきます。

```
% touch .env.local .env.staging .env.production
```

```
// package.json

{
  ...
  "scripts": {
    ...
    "dev": "nuxt dev --dotenv .env.local",
     ...
    "generate": "nuxt generate --dotenv .env.local",
    "generate:staging": "nuxt generate --dotenv .env.staging",
    "generate:production": "nuxt generate --dotenv .env.production",
    ...
  },
}
```

```
// 本番用ファイルを書き出すとき
% yarn generate:production
```

## CSSプリプロセッサ

対応するプリプロセッサ自体をインストールするだけで、設定なしで`.scss`、`.sass`、`.less`、`.styl`、`.stylus`ファイルのサポートします。

個人的にはpostCSS推しですが、利用頻度が高いscssを例に記載します。

### SCSSのインストール

```
% yarn add -D sass
```

### SCSSのグローバル スタイルのインポート

[ドキュメント](https://nuxt.com/docs/getting-started/assets#global-styles-imports)に倣って、変数・mixinを共通で使える用にしてみます。

```
// valiabesファイルを生成
% mkdir -p src/assets/styles
% touch src/assets/styles/_valiabes.scss
```

``` typescript
// nuxt.comfog.js
export default defineNuxtConfig({
  ...
  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: '@use "~/assets/styles/_valiabes.scss" as *;'
        }
      }
    }
  },
  ...
})
```

# ESlint・Stylelint関連

コードの一貫性を維持するためにESlintやStylelintを導入して、構文エラーを見つけられるようにしたりベストプラクティスを機械的に強制していきます。

また、本稿では、VScodeでnuxt3を利用する際にオススメ（prettierを含めた、自動整形機能等が矛盾なく効くようにする設定）を定義ファイルを作成します。

※vscodeの自動整形に関しての考察は、長くなっちゃう＆しょっちゅう変わるので割愛。いずれ記事にします。自動整形（フォーマット）機能は変化が激しいので、一定期間経つと動作しない場合があります。相談があれば別途コメントでもしてください。\
※設定ファイルは`.cjs`を利用します。未だ一部のモジュール等でCommonJSが使われている場合があり、且つ、コメントを入れられるようにするためです。（コメントがない基本設定とか鬼畜の所業だと思う。）

## ESlintの設定

Typescript利用の前提です。
Nuxtでは、[ロードマップ](https://github.com/nuxt/eslint-config/issues/303)が公開されていて、今でもどうするのがいい？と議論が行われています。変化についていくことは~~かなり~~大変なので、試した中で一番シンプルな方法を記載しておきます。

設定の内容は以下です。

0. typescriptの型チェックを有効化
0. npmパッケージのインストール
0. .eslintrc.cjs（定義ファイル）の設置
0. yarnコマンドでESlintチェック
0. eslintエラーがある時はビルドに失敗させる


### typescriptの型チェックを有効化

Nuxtの[ドキュメント](https://nuxt.com/docs/guide/concepts/typescript)を参考に、常にtypescriptの型チェックを行うようにします。\
注意点として、常にチェックすることはパフォーマンスが悪いため、開発が進むと動作が重くなります。その時は`typeCheck: false`へ変更し、コミット前や作業毎に`npx nuxi typecheck`をする癖をつけてください。

※常にチェック入れると、` ERROR [vue-tsc] Found 0 errors. Watching for file changes.`と出る。`vite-plugin-checker`の[修正待ち](https://github.com/fi3ework/vite-plugin-checker/issues/238)。


```
// 必要なパッケージのインストール
% yarn add -D vue-tsc typescript
```

``` typescript
// nuxt.config.ts

export default defineNuxtConfig({
  ...
  typescript: {
    // 型を常にチェック
    // memo:動作が重い場合はfalseへ変更
    typeCheck: true
  },
  ...
})
```

`npx nuxi typecheck`を忘れがちの方は、package.jsonへコマンドを追加しておくと良いでしょう。

```
// package.json
{
  ...
  "scripts": {
    ...
    "typecheck": "nuxi typecheck",
    ...
  },
  ...
}
```


### npmパッケージのインストール

[ドキュメント](https://nuxt.com/docs/guide/concepts/code-style#eslint)を参考に、eslintの設定をします。

```
% yarn add -D eslint @nuxt/eslint-config
```

### .eslintrc.cjs（定義ファイル）の設置

ESlintの定義ファイルを作成して、npmパッケージの取り込みおよび、オススメの定義を記載します。方針として、prettierに任せられる場所は任せていきます。

```
% touch .eslintrc.cjs
```

``` typescript
// .eslintrc.cjs
module.exports = {
  // 意図せず設定ファイル遡らない様にする
  root: true,
  // モジュールを読み込み。これだけでもOK。
  extends: ['@nuxt/eslint-config'],

  // 以下ルールは、オススメの設定
  rules: {
    /**
     * prettierとeslintの競合回避
     */
    // 行の最大長ルールを無効化(prettierに任せる)
    'max-len': 'off',
    // 一行時の属性値数チェックを無効化(prettierに任せる)
    'vue/max-attributes-per-line': 'off',
    // インデントルールを無効化(prettierに任せる)
    'vue/html-indent': 'off',
    // マルチライン時のインデントルールを無効化(prettierに任せる)
    'vue/multiline-html-element-content-newline': 'off',
    // タグの閉じ括弧の前に改行設定を無効化(prettierに任せる)
    'vue/html-closing-bracket-newline': 'off',
    // 単一行要素の内容の前後に改行設定を無効化(prettierに任せる)
    'vue/singleline-html-element-content-newline': 'off',
    // 複数行の時に末尾のカンマを許容
    'comma-dangle': ['error', 'only-multiline'],

    /**
     * 好みの設定
     */
    // imgのセルフクローズを矯正
    'vue/html-self-closing': [
      'error',
      {
        html: {
          void: 'always',
        },
      },
    ],
    // 不要な変数に対して警告を出す（_から始まるものは許容）
    '@typescript-eslint/no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^_', // 引数
        varsIgnorePattern: '^_', // 変数
        caughtErrorsIgnorePattern: '^_', // errorハンドリング
        destructuredArrayIgnorePattern: '^_', // 配列内の変数参照
      },
    ],
    // コンポーネント名のマルチワード警告を無効化
    'vue/multi-word-component-names': 'off',
  },
}
```

### yarnコマンドでESlintチェック

`yarn run lint:script --fix`で、ルールによって報告された違反を自動的に修正できるようにします。

```
// package.json
{
  ...
  "scripts": {
    ...
    "lint:script": "eslint --ext \"src/**/*.js,.ts,.vue\" --ignore-path .gitignore .",
    ...
  },
  ...
}
```

### eslintエラーがある時はビルドに失敗させる

[@nuxtjs/eslint-module](https://nuxt.com/modules/eslint)を利用すると、eslint エラーがある場合にプロジェクトがビルドに失敗するようできます。ドキュメントに倣ってインストールします。

```
% yarn add -D @nuxtjs/eslint-module
```

nuxt.config.tsへモジュールの追加し、lintチェックが走るように修正します。起動時lintチェックはパフォーマンスが悪いため、開発が進むと動作が重くなります。その時は、`lintOnStart: false`へ変更してください。

``` typescript
// nuxt.config.ts
export default defineNuxtConfig({
	...
  modules: [
    // https://nuxt.com/modules/eslint
    [
      '@nuxtjs/eslint-module',
      {
        // 起動時lintチェック
        // memo:動作が重い場合はfalseへ変更
        lintOnStart: true,
      },
    ],
    ...
  ],
  ...
})
```

## Stylelintの設定

SCSS利用の前提です。また、cssプロパティの並び順も指定します。~~揃ってないとスタイリングのコードレビューがキツイ~~

設定の内容は以下です。

0. npmパッケージ群をインストール
0. stylelintrc.cjsファイル （Stylelintの定義ファイル）の設置
0. yarnコマンドでLintチェックができるように設定
0. ~~nuxtでStylelintのエラーを拾えるようにする~~

### npmパッケージ群をインストール

```
% yarn add -D stylelint stylelint-config-standard-scss postcss-html stylelint-config-standard-vue stylelint-config-recess-order
```

### stylelintrc.cjsファイルの設置

`.stylelintrc.cjs `定義ファイルを作成し、パッケージの取り込みおよび、オススメの設定（prettierを含めた、自動整形機能等が矛盾なく効くようにする設定を見越して）を定義します。~~rulesの中身は、自分の好みかいてます。~~

```
% touch .stylelintrc.cjs
```

``` javascript
// .stylelintrc.cjs
module.exports = {
  // lint機能を追加
  extends: [
    'stylelint-config-standard', // cssファイル対応（忘れがち）
    'stylelint-config-standard-scss', // scssに対応
    'stylelint-config-standard-vue/scss', // vueファイルに対応
    'stylelint-config-recess-order', // プロパティ並び替え
  ],
  //各種ルールを追加（正直好みで書いてるのでなくても良い）
  rules: {
    /**
     * vueファイルへ対応
     */
    'block-no-empty': null, // 空のブロックを許容する
    'no-empty-source': null, // 空のstyleブロックを許容する

    /**
     * Scssの記述に対応
     */
    'font-family-no-missing-generic-family-keyword': null, // font-familyに関する指定をしない
    'no-descending-specificity': null, // 詳細度並び順の指定をしない
    'comment-empty-line-before': null, // コメント前へ改行を入れる指定をしない
    'at-rule-empty-line-before': null, // @前へ改行をいれる指定をしない
    'declaration-empty-line-before': null, // @後や--後へ改行をいれる指定をしない

    /**
     * プロジェクトごとに指定
     */
    // grid/borderをまとめない
    'declaration-block-no-redundant-longhand-properties': [
      true,
      {
        ignoreShorthands: ['/^grid.*/', '/border/'],
      },
    ],
    // BEMの命名規則を許容
    'selector-class-pattern': '^[a-z][a-z0-9_-]+$',
    'selector-id-pattern': '^[a-z][a-z0-9_-]+$',
    // コメントの後の空白に関する指定をしない（ちゃんとしたエラー出ないので解除）
    'scss/double-slash-comment-whitespace-inside': null,
    // セレクタータイプチェックの除外指定
    // 'selector-type-no-unknown': [
    //   true,
    //   { ignoreTypes: ['_', 'x'] }, // チェック除外項目
    // ],
  },
}

```

Tips：最終的にどんなruleになっているか確認したい場合は、`npx stylelint .stylelintrc.cjs --print-config`で確認できます。

### yarnコマンドでLintチェックができるように設定

`yarn run lint:style --fix`で、ルールによって報告された違反を自動的に修正できるようにします。
合わせて、`yarn lint`でtypescrip、eslint、stylelintが同時に実行できる様にしておきます。

```
// package.json
{
  ...
  "scripts": {
    ...
    "lint:style": "stylelint \"src/**/*.{css,scss,sass,vue}\" --ignore-path .gitignore",
    "lint": "yarn typecheck && yarn lint:script && yarn lint:style"
    ...
  },
  ...
}

```

### nuxt3で、stylelintのエラーを拾えるようにする

~~[@nuxtjs/stylelint-module](https://nuxt.com/modules/stylelint)あるので、ドキュメントに倣ってインストールします。~~

lintエラー拾いが個人の希望通り設定できなかったため割愛。\
最低限のチェックはディフォルトで行えます。また、機能拡張等を併用することである程度カバーができるため、時間をおいて検討予定という方針。

基本的には、VScodeの設定と`yarn lint:style`でチェックを実施していく。


# vscodeで保存時の自動整形機能を有効にする

VScodeの開発スピードは速いため、自動整形関連を長期間担保するのかなりキツイです。(検索で出てくる記事の更新日に注意してください。)\
しかしながら、メンバーの開発効率が格段に上がるのも事実なので、基本構築する担当者や指導する立場にある人は、最低限できると良いでしょう。

また、自動保存等のVScodeのセッティングを、ある程度外部ファイル化して共有することができます。個人の好みによる機能拡張周りの設定による不具合は…知らん。

手順は以下です。

1. 必要な機能拡張を追加
2. prettierの設定ファイルを作成
3. vscodeのsetting.json作成
4. 再起動

## 必要な機能拡張を追加

### 機能拡張「editorconfig for VS code」

[editorconfig for VS code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig)をインストールします。新規ファイル生成時などに.editerconfigファイルの設定が反映されます。

特に機能拡張の設定は必要ありません。

### 機能拡張「Volar」

[Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)にアクセスしてinstallボタンを押して…詳細は割愛します…インストールします。vueファイルのシンタックスハイライトに必要です。

特に機能拡張の設定は必要ありません。

### 機能拡張「eslint」

[eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)をインストールします。.eslintrc.cjsファイルの設定が反映されます。

### 機能拡張「stylelint」

stylelintが公式にリリースした[stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint)をインストールします。同じ名前の機能拡張に[stylelint](https://marketplace.visualstudio.com/items?itemName=Daosro.stylelint)がありますが、公式じゃないので注意してください。

### 機能拡張「Prettier」

[Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)をインストールします。

## prettierの設定ファイルを作成

prettier v3.x系を利用したいので、パッケージをインストールします。

```
% yarn add -D prettier
```

prettierの定義ファイルを作成します。作成後、vscodeの再起動を行なってください。

```
% touch .prettierrc.cjs
```

``` javascript
// .prettierrc.cjs
module.exports = {
  /**
   * prettierとeslintの競合回避
   */
  // prettierの最大長ルールを少なくしてる人がいるのでディフォルト値を追加
  printWidth: 80,
  // エラーが発生する場合「のみ」末尾にセミコロンを出力
  semi: false,
  // 二重引用符の代わりに一重引用符を使用
  singleQuote: true,
  //　可能な限り末尾にカンマを付ける
  trailingComma: 'all',

  /**
   * 好みの設定
   */
  // アロー関数パラメータを括弧で囲む。型情報つけるので必須(ディフォルト値always)。
  arrowParens: 'always',

　　　　/**
   * phpも自動フォーマットしたい
   */
  // plugins: ['@prettier/plugin-php'],
}
```

```
// pacage.json
  "scripts": {
    ...
    "fix": "yarn prettier && yarn lint:script --fix && yarn lint:style --fix"
  },
```

## vscodeのsetting.json作成

.vscodeディレクトリに、各種設定を書いた`settings.json`を、また、必要な機能拡張を書いた`extensions.json`をおくことで、プロジェクト毎に共有することができます。

設定内容はコメントを参照してください。
```
touch .vscode/settings.json .vscode/extensions.json
```

```
// .vscode/settings.json
{
  // 既存のリンターを解除
  "css.validate": false,
  "less.validate": false,
  "scss.validate": false,

  // 自動フォーマットを起動
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll": "never",
    "source.fixAll.eslint": "explicit",
    "source.fixAll.stylelint": "explicit"
  },

  // eslint/stylelintの対象ファイルを指定
  "eslint.validate": ["js", "cjs", "ts", "vue"],
  "stylelint.validate": ["css", "scss", "vue"],

  // ディフォルトフォーマッタにprettierを指定
  "editor.defaultFormatter": "esbenp.prettier-vscode",

  // 形式ごとのフォーマッタ指定を追加
  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[tyepscript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },

  // markdownは自動フォーマットしない
  "[markdown]": {
    "editor.formatOnSave": false
  },

  // phpも自動フォーマットしたい
  // need: [Run on Save](https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave)
  // "emeraldwalk.runonsave": {
  //   "commands": [
  //     {
  //       "match": ".php$",
  //       "cmd": "prettier ${file} --write"
  //     }
  //   ]
  // },

  // liveServerでgenerateファイルを確認したい
  // need: [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
  // "liveServer.settings.root": "/dist"
}
```

```
// .vscode/extensions.json
{
  // 推奨機能拡張
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "stylelint.vscode-stylelint",
    "editorconfig.editorconfig",
    "vue.volar"
    // "emeraldwalk.runonsave",
    // "ritwickdey.liveserver",
  ],

  // 非推奨の機能拡張
  "unwantedRecommendations": [
    "octref.vetur"
  ]
}
```

### 再起動

VScodeの設定関連を反映するためには、vscodeの再起動が必要です。\
一旦閉じて、無事動作するか確認しましょう。

# generateのために行う設定や注意点

やっと本題です。忘れがちな設置をはじめ、SEO周りの注意点を記載します。全て、後から変更可能です。

## 余分なファイルをgenerateから避ける

デフォルトでは、Nuxt3は存在するすべてのページを事前レンダリングします。そのため、コードの見通しをよくするための単発利用コンポーネントを`/pages`内に配置すると、generate時にファイルを生成してしまいます。\
なので、hookを使い、Pages内の大文字から始まるファイルを除外するように指定します。

Tips：余分なファイルレンダリングをちらほら見かけるので、プロジェクトの仕様に合わせて書き換えてください。

``` typescript
// nuxt.config.ts
import type { NuxtPage } from 'nuxt/schema'

export default defineNuxtConfig({
  　...
  hooks: {
    // 参考：https://nuxt.com/docs/guide/going-further/custom-routing#pages-hook
    'pages:extend'(pages) {
      // pages内の大文字から始まるファイルを除外
      function removePagesMatching(pattern: RegExp, pages: NuxtPage[] = []) {
        const pagesToRemove = []
        for (const page of pages) {
          const filename = page.file ? page.file.match(/[^/]+$/)?.[0] : undefined
          if (filename && pattern.test(filename)) {
            pagesToRemove.push(page)
          } else {
            removePagesMatching(pattern, page.children)
          }
        }
        for (const page of pagesToRemove) {
          pages.splice(pages.indexOf(page), 1)
        }
      }
      removePagesMatching(/^[A-Z].*$/, pages)
    },
  },
  ...
})
```

### 意図的に事前レンダリングを禁止

事前レンダリング(generate)したくないディレクトリがある場合があります。合わせて、設定を記載しておきます。

``` typescript
// nuxt.config.ts
export default defineNuxtConfig({
　...
  nitro: {
    prerender: {
      /**
       * 意図的に事前レンダリングを禁止
       * docs:https://nitro.unjs.io/config#prerender
       */
      ignore: [
        // 禁止ディレクトリを追加
        '/no-generate',
      ],
    },
  },
	...
})
```

## 最低限のページ作成

細かな設定をしていくにあたり必要なページの生成します。

```
% mkdir src/pages src/layouts
% touch src/pages/index.vue src/layouts/default.vue
```

``` vue
// pages/index.vue
<template>
  <div>index</div>
</template>
```

``` vue
// layouts/default.vue
<template>
  <div>
    <header>header</header>
    <slot />
    <footer>footer</footer>
  </div>
</template>
```

``` vue
// app.vue
<template>
  <div>
    <NuxtLayout>
      <NuxtPage />
    </NuxtLayout>
  </div>
</template>
```

## 404エラーページ

[公式ドキュメント](https://nuxt.com/docs/getting-started/error-handling#error-page)を参考にerrorページを作成します。

Tips: generate時にerrorページを404.htmlファイルとして生成します。

```
% touch src/error.vue
```

``` vue
// error.vue
<template>
  <div>
    <NuxtLayout>
      <!-- 404 -->
      <template v-if="error && error.statusCode === 404">
        <h1>{{ error.message }}</h1>
        <h2>音速で探しましたがページが見つかりません😱</h2>
        <p>
          申し訳ありませんが、お客さまがお探しのページが見つかりませんでした。
          <br />ページが移動したか、もしくは掲載期間が終了した可能性がございます。
          <br />お手数ですが、「<a @click="toTop">トップページに戻る</a
          >」を押して、ご覧になりたいページをお探しください。
        </p>
        <!-- 開発環境のみで表示 -->
        <pre v-if="isDev">{{ error }}</pre>
      </template>

      <!-- 404以外 -->
      <template v-else>
        <h1 v-if="error">{{ error.message }}</h1>
        <h2>ページが表示できません😱</h2>
        <p>
          ご不便をおかけし申し訳ありません。
          <br />正常にご覧いただけるよう、解決に取り組んでおります。
          <br />しばらく時間をおいて再度ご覧ください。
        </p>
        <small v-if="error">{{ error?.statusCode }} error</small>
        <!-- 開発環境のみで表示 -->
        <pre v-if="isDev">{{ error }}</pre>
      </template>

      <br />

      <!-- トップへ戻る -->
      <button @click="toTop">トップページに戻る</button>
    </NuxtLayout>
  </div>
</template>

<script setup lang="ts">
defineProps({
  error: {
    type: Object,
    default: () => ({}),
  },
})

/**
 * 開発環境かどうかのチェック
 */
const isDev = import.meta.dev

/**
 * エラーを削除してトップへ遷移
 */
function toTop() {
  clearError({ redirect: '/' })
}
</script>
```

Apacheサーバーのために`.htaccess`を準備しておきます。\
.htaccessはmod_rewriteで記述しています。サーバー仕様に合わせて変更してください。サーバーの管理画面から変更できる場合はそちらも検討してください。

```
touch src/public/.htaccess
```

```
<IfModule mod_rewrite.c>

# RewriteEngineをOn、ドキュメントルートを設定する ---
RewriteEngine on
RewriteBase /

# 404リダイレクト ---
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.*$ 404.html

</IfModule>
```


<details>
<summary>個人的によく使う.htaccessの記述</summary><div>

```
<IfModule mod_rewrite.c>

# --- RewriteEngineをOn、ドキュメントルートを設定する ---
RewriteEngine on
RewriteBase /

# --- index.html/phpなしに統一 ---

RewriteCond %{THE_REQUEST} ^.*/index.html
# ↓特定のディレクトリを対象外にする
# RewriteCond %{REQUEST_URI} !(xxx/)
RewriteRule ^(.*)index.html$ $1 [R=301,L]

RewriteCond %{THE_REQUEST} ^.*/index.php
# ↓特定のディレクトリを対象外にする
# RewriteCond %{REQUEST_URI} !(xx/)
RewriteRule ^(.*)index.php$ $1 [R=301,L]

# --- wwwあり・なしに統一 ---

# あり
# RewriteCond %{HTTP_HOST} ^example\\.com$
# RewriteRule ^(.*)$ <https://www.example.com/$1> [R=301,L]

# なし
RewriteCond %{HTTP_HOST} ^www\\.example\\.com$
RewriteRule ^(.*)$ <https://example.com/$1> [R=301,L]

# --- httpsに統一 ---

RewriteCond %{SERVER_PORT} 80
# ↓特定のディレクトリを対象外にする
# RewriteCond %{REQUEST_URI} !(xxx/)
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

# --- ディレクトリダイレクト ---

RewriteRule ^oldDir/(.*)$ newDir/$1 [R=301,L]

# --- ページリダイレクト ---

RewriteRule ^oldDir/index.html newDir/new.html [R=301,L]

# --- 404リダイレクト ---

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.*$ 404.html

</IfModule>

# キャッシュコントロール
# Thanks：<https://html5boilerplate.com/>

<IfModule mod_expires.c>

    ExpiresActive on
    ExpiresDefault                                      "access plus 1 month"

  # CSS
   ExpiresByType text/css                              "access plus 1 year"

  # Data interchange
    ExpiresByType application/json                      "access plus 0 seconds"
    ExpiresByType application/xml                       "access plus 0 seconds"
    ExpiresByType text/xml                              "access plus 0 seconds"

  # Favicon (cannot be renamed!)
    ExpiresByType image/x-icon                          "access plus 1 week"

  # HTML components (HTCs)
    ExpiresByType text/x-component                      "access plus 1 month"

  # HTML
    ExpiresByType text/html                             "access plus 0 seconds"

  # JavaScript
    ExpiresByType application/javascript                "access plus 1 year"

  # Manifest files
    ExpiresByType application/x-web-app-manifest+json   "access plus 0 seconds"
    ExpiresByType text/cache-manifest                   "access plus 0 seconds"

  # Media
    ExpiresByType audio/ogg                             "access plus 1 month"
    ExpiresByType image/gif                             "access plus 1 month"
    ExpiresByType image/jpeg                            "access plus 1 month"
    ExpiresByType image/png                             "access plus 1 month"
    ExpiresByType video/mp4                             "access plus 1 month"
    ExpiresByType video/ogg                             "access plus 1 month"
    ExpiresByType video/webm                            "access plus 1 month"

  # Web feeds
    ExpiresByType application/atom+xml                  "access plus 1 hour"
    ExpiresByType application/rss+xml                   "access plus 1 hour"

  # Web fonts
    ExpiresByType application/font-woff                 "access plus 1 month"
    ExpiresByType application/vnd.ms-fontobject         "access plus 1 month"
    ExpiresByType application/x-font-ttf                "access plus 1 month"
    ExpiresByType font/opentype                         "access plus 1 month"
    ExpiresByType image/svg+xml                         "access plus 1 month"

</IfModule>
```

</div>
</details>

## SEO対策

[@nuxtjs/seo](https://nuxtseo.com/)のおかげで、バラバラだったseoパッケージ群が統合され、かなりラクにSEOのベストプラクティス~~かな？~~を導入できます。\
まずは、パッケージをインストールしつつ、[推奨構成](https://nuxtseo.com/nuxt-seo/guides/configuring-modules)を記載します。

```
yarn add -D @nuxtjs/seo
```

``` typescript
// nuxt.config.ts
export default defineNuxtConfig({
	...
  modules: [
		...
    // https://nuxtseo.com/
    '@nuxtjs/seo',
  ],
  
  // nuxtjs/seoの設定
  site: {
    defaultLocale: 'ja',
    url: 'http://localhost:3000',
    name: '株式会社サイト名',
    description: '共通のディスクリプション', 
    // URL末尾にスラッシュを付与する
    trailingSlash: true, 
  },
  ...
})
```

`yarn generate`を実行してdistフォルダ内を確認すると、sitemap.xmlをはじめ、必要なファイル群が書き出されていることが確認できます。

あとは、お好みで各種設定を追加していきます。

### URL末尾にスラッシュを付与する

Nuxtは、ディフォルトではURLの末尾に`/`がつきません。先ほどの`nuxtjs/seoの設定`で、`trailingSlash: true`を追加することで末尾のスラッシュを強制できます。

Tips：`defineNuxtLink`のオプションでも末尾スラッシュの追加は可能ですが、こちらの方がお手軽です。

### 本番環境・ステージング環境・開発環境で分けたい

urlをはじめとした環境ごとに違う設定を`.env`ファイルで管理します。
`baseURL`の項で作成した、.envのカスタムファイル（`.env.local``.env.staging``.env.production`）に以下を追記します。

``` env
# .env.local | staging | production

# ディレクトリ構造に合わせる
# NUXT_APP_BASE_URL=/my-dir

# 環境のURL
NUXT_SITE_URL=http://localhost:3000 # or https:staging.example.com | https://example.com

# 環境
NUXT_SITE_ENV="develoment" # or staging　| production
```

### robots.txtの設定

[@nuxtjs/robots](https://nuxtseo.com/robots/getting-started/installation)のドキュメントを参考に設定します…。が、ほとんど場合何もしなくて大丈夫です。後で調べ直さなくていいように、例を記載しておきます。

注意点としては、`NUXT_SITE_ENV === 'production’`以外では`<meta name="robots" content="noindex, nofollow"> `が挿入されインデックスされません。書き出されたファイルをちゃんとチェックしないと、後で泣きます。

``` typescript
// nuxt.config.ts

export default defineNuxtConfig({
  ...
  // robots.txt(nuxt/seo)
  // memo: NUXT_SITE_ENV === 'production' 以外では全て無効化される
  robots: {
    // disallow: ['/admin'], // 無効化リスト
  },
  ...
})
```

#### sitemap.xmlの設定

[@nuxtjs/sitemap](https://nuxtseo.com/sitemap/getting-started/installation)のドキュメントを参考に設定します…。が、こちらも、ほとんど場合何もしなくて大丈夫です。

個人的に余分なファイルは出力したくないため、sitemap.xml用のスタイルシートとクレジットを無効化しておきます。

Tips：robots.disallowで指定したパスはサイトマップに含まれないので安心です。

``` typescript
// nuxt.config.ts

export default defineNuxtConfig({
  ...
  // sitemap.xml(nuxt/seo)
  // memo: robots.disallowで指定したパスはサイトマップに含まれない
  sitemap: {
    xsl: false, // sitmap.xmlのスタイルシートを無効化
    credits: false, // サイトマップのクレジットを無効化
  },
  ...
})
```

### og:imageの設定

[nuxt-og-image](https://nuxtseo.com/og-image/getting-started/installation)のドキュメントを参考に設定します…。が、OGP画像の自動生成を実施するとパフォーマンスが重いため扱いに気をつけましょう。\
[`@nuxt/content`](https://content.nuxt.com/)の利用を想定している場合などはとても強力なツールですが、自動生成を利用しない場合は、素直に`useHead`や`useSeoMeta`を利用した方が楽なので、プロジェクトに合わせて検討をしてください。

Tips：2024/1/22現在、baseURLを指定してるとgenerate時にogp画像へのリンクが壊れます。今後の開発に期待。

設定サンプルを記載しておきます。

``` typescript
// nuxt.config.ts

export default defineNuxtConfig({
  ...
  // og:image(nuxt/seo)
  // memo: 無効化したい場合は、enabled: falseを指定
  // memo: 変更が反映されない場合はキャッシュの無効化を試す。
  ogImage: {
    // enabled: false, // 無効化
    // runtimeCacheStorage: false, // キャッシュの無効化
    fonts: ['Noto+Sans+JP:700'], // 日本語使えるフォントを指定
  },
  ...
})
```

``` vue
// app.vue 全ページ共通の画像ベース例

<script lang="ts" setup>
const siteConfig = useSiteConfig()
defineOgImage({
  url: 'https://amiten.co.jp/ogp/home.png', // 絶対URLを利用
  alt: siteConfig.name,
})
</script>
```

``` vue
// pages/hoge.vue 下層ページで画像ベース例

<script lang="ts" setup>
defineOgImage({
  url: 'https://amiten.co.jp/ogp/hoge.png', // 絶対URLを利用
})
</script>
```

``` vue
// pages/fuga.vue 下層ページで自動生成の例
// Nuxt DevToolsにアクセスしたら、コマンドパレット(Ctrl+K)を開いて「og」と入力することで、`OG Image`タブに移動できます。

<script lang="ts" setup>
useSeoMeta({
  title: 'fugaページ',
  description: 'これはfugaページのディスクリプションです',
})

defineOgImageComponent('Nuxt', {
  headline: 'Fugas-Category',
})
</script>
```

#### schema.orgの設定

[nuxt-schema-org](https://nuxtseo.com/schema-org/getting-started/installation)のドキュメントを参考に設定します…。が、こちらも、ほとんど場合何もしなくて大丈夫です。設定するのは、組織のサイトか個人サイトかの指定部分ぐらいでしょうか？

参考：[ページ詳細の設定](https://nuxtseo.com/schema-org/guides/nodes)。

``` typescript
// nuxt.config.ts

export default defineNuxtConfig({
  ...
  // schema.org(nuxt/seo)
  schemaOrg: {
    identity: 'Organization', // or 'Person'
  },
  ...
})
```

### Nuxt/Seoのその他機能

- [Nuxt Link Checker](https://nuxtseo.com/link-checker/getting-started/installation)\
リンクやURLのチェックをしてくれます。個人的には細かく設定してチェックをしたいのだけど、プロジェクト毎に変わるので割愛。
- [Nuxt SEO Experiments](https://nuxtseo.com/experiments/getting-started/installation)\
実験的機能なので割愛。個人的には好きな方向性~~でもパフォーマンス重そう~~。

ディフォルトが「有効」になっているので、両方とも無効化する。

``` typescript
// nuxt.config.ts

export default defineNuxtConfig({
  ...
  // linkChecker(nuxt/seo)
  linkChecker: { enabled: false },
  // 実験的機能(nuxt/seo)
  seoExperiments: { enabled: false },
  ...
})
```

## favicon.icn

~~[Nuxt SEO Experiments](https://nuxtseo.com/experiments/getting-started/features)でいけそうなんだけどgenerate時の挙動を確認取れなかったので…~~

nuxtのディフォルト機能で設定します。\
各種ブラウザ用のファビコン`favicon.icon`を作成し、staticディレクトリ直下へ設置します。

Tips：favicon.icon用画像（256x256）を透過.pngで作成し、[Favicon ジェネレーター](https://favicon-generator.mintsu-dev.com/)などで、マルチファビコンを作成すると便利です。

``` typescript
// nuxt.config.ts
export default defineNuxtConfig({
  ...
  app: {
    ...
    head: {
      link: [
        // favicon
        {
          rel: 'icon',
          type: 'image/x-icon',
          href: `${process.env.NUXT_APP_BASE_URL ?? ''}/favicon.ico`,
        },
      ],
    },
    ...
  },
  ...
})
```

## GTMの設置

[@zadigetvoltaire/nuxt-gtm](https://nuxt.com/modules/nuxt-gtm)のドキュメントを参考に設定します…。が、こちらも、ほとんど場合何もしなくて大丈夫です。でも、IDは必須です。

```
% yarn add -D @zadigetvoltaire/nuxt-gtm
```

``` typescript
// nuxt.config.ts

module.exports = {
  modules: [
    ...
    // https://github.com/nuxt-community/gtm-module
    '@zadigetvoltaire/nuxt-gtm',
  ],
  ...
  // nuxt-gtm(IDは各自変更)
  gtm: {
    id: 'GTM-XXXXXXXX',
  },
}
```

gtmを利用した広告関連のためにタイトルとパスを渡す必要が出てくることがあります。
GTMへのイベント発行用composableを記載しておきます。\
↓
``` typescript
// composables/gtmEvemt.ts

interface GtmEventOptions {
  title: string
  path: string
}

/**
 * GTMへのイベント発行
 * @description
 * - gtmを利用した広告関連のためにタイトルとパスを渡す
 */
export const gtmEvent = (options: GtmEventOptions) => {
  onMounted(() => {
    nextTick(() => {
      // gtmの取得
      const gtm = useGtm()
      if (!gtm) return

      // gtmの発火
      gtm.trackEvent({
        event: 'loadready', // event名はgtm管理者へ確認
        trackPageTitle: options.title,
        trackPageview: options.path,
      })
    })
  })
}
```

### いやいや、うちはGAだから…

[Nuxt Gtag](https://nuxt.com/modules/gtag)を参照してください。

## ハッシュリンクのスクロール動作

[ドキュメント](https://nuxt.com/docs/guide/going-further/custom-routing#scroll-behavior-for-hash-links)を参考に入れてください。
↓
``` typescript
// nuxt.config.ts
export default defineNuxtConfig({
  ...
  router: {
    options: {
      scrollBehaviorType: 'smooth'
    }
  }
  ...
})
```

---

### おまけ

ご指摘お待ちしております。

nuxt3.9になり、気になっていたvueのバグもvue3.4系で修正されました。こなれてきた感じがありましたので、一通りまとめさせていただいきました。

また、テキスト量を減らすため言い切り型の言い回しが多いですが、プロジェクトに合わせて柔軟に対応してもらえると嬉しいです。説明にはできるだけ公式ドキュメントのリンクを貼っていますので参考にしてください。

Nuxt.jsをきっかけにesLintやstyleLintを使い始めたメンバーも多く見受けられます。相変わらず変化スピードは追っかけてる身としては少し辛い（褒めている）ですが、プロジェクトをこなすたびに自然と時代の流れに乗ることができる点も評価が高いです。

それにしても…。

もっとシンプルに生きたい。