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のルーティングを少しばかりいじれば良いのです。NuxtのルーターはVue Routerで、Vue Routerにはエイリアスが設定できます。これを用いて、/index.htmlを/のエイリアスにしてしまえば良いはず。
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という設定があり、上記のように/fooディレクトリを作るのではなくfoo.htmlを作るように変更ができます。
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する時だけルーティングを変更します。無理やり変更したものをさらに無理やり変更する。危険な香りがしますね。
nuxt generateで特定のファイルだけ生成したいという投稿が参考になりました。moduleを作って入れるのでももちろん良いですが、configにも書けるようになっています。とりあえず手間なくこんな感じで。
hooks: {
generate: {
async extendRoutes(routes) {
const filtered = routes.filter(page => page.route != '/index.html')
routes.splice(0, routes.length, ...filtered)
}
}
},
これでnuxt generateすると…
(゚ー゚)(。_。)ウンウン なんとか大丈夫!
結論
DirectoryIndexの都合で/index.htmlにアクセスしなければいけない時は…
-
router.extendRoutesで/index.htmlのエイリアスを追加する -
hooks.generate.extendRoutesでgenerate時だけ/index.htmlのrouteを除去する
おまけ
最終的に、index.htmlだけじゃなくて他の*.htmlもどうにかしたくなったのでこうなりました。
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)
}
}
},
おしまい
もっといい方法ありそうだから、知ってたら教えてね。