nuxt generateして出来たindex.htmlにアクセスすると正しく動かない件

Nuxt便利ですね。SSRやSPAも良いですが、静的サイトをちょろっと作るときも、`nuxt generate`を使ってコンポーネントベースでさっくり作れたりします。素晴らしい。

**ところで。**

唐突ですが、DirectoryIndexはご存知でしょうか。ファイル名なしのディレクトリ`https://example.com/foo/bar/`にアクセスした時に、何が表示されるか、という設定です。通常`index.html`あたりが設定されていますが、それは絶対的ルールではありません。

サーバにはディレクトリ内のファイルを自動的に一覧してくれる機能とかもあって、まあ公開サーバなんかでは一覧されるとよろしくないことが多いので使わないことが多いですが、テストサーバなどではむしろ一覧を作って欲しい時もあります。そんなとき、`GET /`はファイル一覧、`GET /index.html`がインデックスファイルの表示、です。

つまり。`/`と`/index.html`は違うもの、です。

# `/`と`/index.html`は違うもの

Nuxt的にも（Vue Router的にも）両者は違うものです。Nuxtはpagesディレクトリのファイル構成からルーティングを自動生成し、`nuxt generate`はルーティングからファイルを生成します。そこのところがすこーしややこしい状況になります。

| path            | route | file            |
|-----------------|-------|-----------------|
| pages/index.vue | /     | dist/index.html |

`index.vue`から`index.html`が作られる、というのはまあ良いのですが、しかしその`index.html`のrouteはあくまでも`/`であって、`/index.html`ではない。ので、`/`にアクセスしてDirectoryIndexとして`index.html`が表示されるならば何の問題もないですが、**`/index.html`にアクセスしなければ表示できないとなると、routeが一致しないためきちんと動きません。**

なんとまあ。

これ、あまりにも自明な問題なのか、あるいは設定一発で簡単に解決できるのか、回避する手立てを見かけないのです。自明な問題の解決策を見抜けないわたしは、泥臭く色々してみました。

# ルーティングをいじる

結局のところ、[Nuxtのルーティングを少しばかりいじれば良い](https://ja.nuxtjs.org/api/configuration-router/
)のです。NuxtのルーターはVue Routerで、[Vue Routerにはエイリアスが設定](https://router.vuejs.org/ja/guide/essentials/redirect-and-alias.html)できます。これを用いて、`/index.html`を`/`のエイリアスにしてしまえば良いはず。

```javascript:nuxt.config.js
  router: {
    extendRoutes(routes, resolve) {
      routes.push({
        path     : '/index.html',
        alias    : '/',
        component: resolve(__dirname, 'pages/index.vue')
      })
    }
  },
```

これで開発サーバで`/`と`/index.html`にアクセスすると、きちんと両方とも動きます。これで万事解決！！！

**では残念ながらありません。誠に遺憾。**

試しに`nuxt generate`すると…

```
✖ Nuxt Fatal Error
Error: EISDIR: illegal operation on a directory, open '/Users/aql/tmp/dist/index.html'
```

はてこれは一体。

# ルーティングをベースにgenerateする

繰り返しになりますが、`nuxt generate`はルーティングをベースにして静的生成する部分を決めます。そう、上記で加えたエイリアス設定も生成の対象になってしまうのです。

そして今一度思い出すと、ルーティングと生成ファイルの対応はこんな感じでした。

| path            | route | file                |
|-----------------|-------|---------------------|
| pages/index.vue | /     | dist/index.html     |
| pages/foo.vue   | /foo  | dist/foo/index.html |

`/foo`に対して`/foo/index.html`が生成される。ということは、`/index.html`に対しては`/index.html/index.html`が生成される…。これがエラーの秘密のようです。なんたる事だ…。

# `generate.subFolders`を使う

なんとか逃げる手はないものか。[subFoldersという設定](https://ja.nuxtjs.org/api/configuration-generate/#subfolders)があり、上記のように`/foo`ディレクトリを作るのではなく`foo.html`を作るように変更ができます。

```javascript:nuxt.config.js
  generate: {
    subFolders: false
  },
```

これでディレクトリを作るエラーは回避されますが…生成ファイルとの関係は以下の通り。

| path            | route       | file                 |
|-----------------|-------------|----------------------|
| pages/index.vue | /           | dist/index.html      |
| pages/foo.vue   | /foo        | dist/foo.html        |
| -               | /index.html | dist/index.html.html |

なんだろう、このイマイチ感。動きますけどね。

# generateの時だけルーティングを変更する

仕方がないので、[フックを使ってgenerateする時だけルーティングを変更](https://ja.nuxtjs.org/api/internals-generator/)します。無理やり変更したものをさらに無理やり変更する。危険な香りがしますね。

[nuxt generateで特定のファイルだけ生成したいという投稿](https://github.com/nuxt/nuxt.js/issues/2719)が参考になりました。moduleを作って入れるのでももちろん良いですが、[configにも書けるようになっています](https://ja.nuxtjs.org/api/configuration-hooks/)。とりあえず手間なくこんな感じで。

```javascript:nuxt.config.js
  hooks: {
    generate: {
      async extendRoutes(routes) {
        const filtered = routes.filter(page => page.route != '/index.html')
        routes.splice(0, routes.length, ...filtered)
      }
    }
  },
```

これで`nuxt generate`すると…

<img width="236" src="https://qiita-image-store.s3.amazonaws.com/0/34122/6643c227-3769-88b8-36b1-7bb75fe05f4c.png">

(ﾟｰﾟ)(｡\_｡)ｳﾝｳﾝ なんとか大丈夫！

# 結論

DirectoryIndexの都合で`/index.html`にアクセスしなければいけない時は…

* `router.extendRoutes`で`/index.html`のエイリアスを追加する
* `hooks.generate.extendRoutes`でgenerate時だけ`/index.html`のrouteを除去する

# おまけ

最終的に、`index.html`だけじゃなくて他の`*.html`もどうにかしたくなったのでこうなりました。

```javascript:nuxt.config.js
  generate: {
    subFolders: false
  },
  router: {
    base: process.env.BASE_DIR || '/',
    extendRoutes(routes, resolve) {
      const aliases = routes.map(route => ({
        path     : /\/$/.test(route.path) ? `${route.path}index.html` : `${route.path}.html`,
        alias    : route.path,
        component: route.component
      }))
      routes.push(...aliases)
    }
  },
  hooks: {
    generate: {
      async extendRoutes(routes) {
        const filtered = routes.filter(page => !/\.html$/.test(page.route))
        routes.splice(0, routes.length, ...filtered)
      }
    }
  },
```

# おしまい

もっといい方法ありそうだから、知ってたら教えてね。