◆ route 問わず共通の処理をして必要ならレスポンスをそこで返したい
  ◆ apache の mod_rewrite みたいなことしたい
◆ h.continue を使っても route は最初にマッチしたひとつしか実行できない
  ◆ if 文で別の route に処理を譲れない
◆ pre 機能は事前処理できるけど 個別 route にしか設定できない
  ◆ デフォルトの route の設定には付けれない
◆ onPreHandler を使う
  ◆ handler より前にレスポンスを返す場合は takeover メソッド必須
  ◆ ファイルの有無は自分でチェック必要 (inert に任せれない)

やりたいことは apache の rewrite みたいなものです
public フォルダにアクセスされたパスのファイルがあるとそれを返して なければ指定された route ごとの処理をします

express や koa は middleware を使えばそれだけでできそうなのですが hapi にはグローバルなミドルウェアがないみたいです
全体で登録されたものを順番に実行していくのではなくて 定義した route ごとに設定を書いていくみたいです

h.continue

h.continue を返せばレスポンスを変更せず続けられるというのを見つけました
先に全体にマッチする route を定義して ファイルがあれば返してなければ h.continue を返せばおっけい と思ってやってみると

まず hapi では route は配列で定義できますが 配列の順番に意味はありません
決められたルールで順番が決まりその順番でマッチするか確認して最初にマッチしたものになります

条件が緩く全体にマッチするものは優先度が低いので先にマッチさせることが出来ません

また 試してみると先にマッチできたとしても h.continue で次にマッチする route を実行することは出来ませんでした
route の handler が h.continue を返しても null に置き換える処理になっていました

route の定義など設定ファイル風に書けるところが express 系と違って良い部分なのですが 自由度も低めな印象です

pre

調べてみると pre というオプションが使えるようです
handler を実行する前に実行できるようです

例を見るとそれぞれの route 定義に書いていて 全部の route に書くのはさすがに嫌なので共通なのがないか探すと Hapi.server のオプションに routes で全部の route 共通のデフォルト設定が書けるようです
ここで設定しておけばどの route に対しても pre が実行できる と思ったのですがエラーが起きました

調べてみると同じエラーで issue があって global に pre は設定できないという回答でした

onPreHandler

なにか方法がないか探していると hapi が処理する順番の途中で 処理を組み込めるようです
onRequest や onPreAuth など用意された名前に関数を設定できます
Chrome 拡張機能の webRequest の図みたいなものです

hapi の流れはここにあるものです

onRequest だと最初過ぎで その後に認証などがあるので 静的ファイルなら認証などはいらないというならここでいいですが それ以外はここでやらないほうがよさそうです
もともと pre でやるつもりだったので onPreHandler にしました

const Hapi = require("hapi")
const inert = require("inert")
const path = require("path")
const fs = require("fs")

const server = new Hapi.Server({ port: 80 })

!(async function(){
await server.register(inert)

server.ext("onPreHandler", function(req, h){
const real_path = path.join(__dirname, req.path)
if (fs.existsSync(real_path) && fs.statSync(real_path).isFile()){
return h.file(real_path).takeover()
}
return h.continue
})

server.route([
{
method: "GET",
path: "/{path*}",
async handler(req, h) {
return h.file("index.html")
},
},
{
method: "GET",
path: "/abc/{path*}",
async handler(req, h) {
return "hello"
},
},
])

await server.start()
})()

これで動きましたが 困ったところが 2 つありました
handler 以前のタイミングでレスポンスを確定させて返す場合は takeover メソッドを呼び出す必要があります
ないとエラーになります

もう一つですが ファイルの有無を自分で判断しないといけません
h.file の返り値で判断できたら楽なのですけど 実行時点ではパスの解決だけでファイルの有無をチェックしたり read したりは起きないみたいです
なのでこのタイミングだとファイルがあるかは h.file の返り値であるレスポンスからはわかりません

自分で確認する必要があります
なんか二度手間感がありますね
どうせなら直接自分で readFile して返そうかとも思いましたが inert は単純にファイルを読んで返すだけじゃなくて色々やってくれてるみたいでそれらまで自分でやりたくないのでファイルの有無だけチェックして処理は inert 任せです

コード的にはそこまで複雑にならずにできましたが これがベストな方法かもわかりませんし この辺りは express 系に比べると扱いづらいと感じます

onPreResponse

もうひとつ考えていたのが逆パターンです
route 側で静的ファイルを返すものです

server.ext("onPreResponse", function(req, h){
if(req.res instanceof Error){
return h.file("index.html") // not working
}else{
return h.continue
}
})

server.route([
{
method: "GET",
path: "/{path*}",
async handler(req, h) {
return h.file(req.path.substr(1))
},
},
])

こうすると onPreResponse のところでファイルが見つからないとエラーを取得できます
しかし route を定義するなら onPreResponse のところで自力で分岐させてとなるので framework の書き方を捨ててるようなものなので 良いとは言えません
ただ api 等がない静的ファイル or デフォルトファイルだけなら困りはしないと思います
404 の代わりに常に index.html を返すだけですから

それでも将来的に特別な route が増える可能性がないとは言えないのでやっぱりオススメはできません

それに問題があって onPreResponse で h.file のレスポンスを返しても正しくファイルがクライアントに送信されません
上で書いたように file レスポンスが解決されるタイミングが決まってるのか onPreResponse からレスポンス送信までに file レスポンスが解決されないようです

この方法にするなら自分で readFile して返すことになります