Nuxt3のServer Middleware
Nuxt3のServer Middlewareは、サーバーリクエストの際に実行したい処理を記述する場所です。
「server/middleware」以下に置かれたファイルは自動で読み込まれ実行されます。
Server Middlewareの使い道
注意してほしいのは、全てのサーバーリクエストで実行される点です。
これはサーバーを介したベージ遷移の際にも、useFetch
でAPIエンドポイントを呼び出す際にも実行されます。
公式ドキュメントにあるように、「server/api」ディレクトリ以下のAPIを呼び出す際に、何らかの共通処理を実行したい場合などに活用できそうです。
「server/middleware」のファイルは、独自のルートにマッピングされるAPIルートとは異なり、すべてのリクエストで実行されます。
これは通常、すべてのレスポンスに共通のヘッダを追加したり、レスポンスをログに記録したり、リクエストチェーンで後で使用するために受信リクエストオブジェクトを変更したりできるようにするためのものです。
Server Middlewareを使ってみる
まず「server/middleware」ディレクトリを作成し、その下にts
ファイルを作成します。
% mkdir -p server/middleware && touch $_/api-logs.ts
apiリクエストのログをとるミドルウェアを作成します。
server/middleware/api-logs.ts
import type { IncomingMessage, ServerResponse } from 'http'
export default async (req: IncomingMessage, _res: ServerResponse, next: any) => {
console.log('server/middleware/api-logs.ts')
console.log(`${new Date().toLocaleString()}:`, req.method)
console.log(`${new Date().toLocaleString()}:`, req.url)
console.log('-----------------------------')
next()
}
以上で完了です。
自動で読み込まれるので、これ以上の設定は必要ありません。
Nuxt Middlewareのnext()
next()
は、アプリケーション内の次のミドルウェア関数を呼び出す命令です。
Nuxt3では、next()
を記述しなくても次のミドルウェアファイルが呼ばれます。
export default async (req: IncomingMessage, _res: ServerResponse, _next: any) => {
...
// 削除しても次のミドルウェアが呼ばれる
// next()
}
ただ、公式ドキュメントには以下のようにあるので、next()
は記述した方が良いでしょう。
ミドルウェアがエンドポイントでない場合は、最後に
next
を呼ぶことを忘れないでください。
ログを確認する
トップページに移動して、ページをリロードしターミナルを確認してみましょう。
API呼び出しページのログ
パス/products
に遷移してみます。
/products
を構築するVueファイルには、useFetch
を使用しサーバーサイドからAPIリクエストを投げています。
pages/products/index.vue
<script setup lang="ts">
const { data: products } = await useFetch('/api/shopify/get-products')
</script>
server/api/shopify/get-products.ts
import type { IncomingMessage, ServerResponse } from 'http'
import { CustomProduct } from '~/types/shopify'
import client from './client'
export default async (_req: IncomingMessage, _res: ServerResponse, _next: any) => {
console.log('server/api/shopify/get-projects.ts')
let products: CustomProduct[] = []
await client.product.fetchAll()
.then((response: CustomProduct[]) => (products = response))
return products
}
この時のServer Middlewareのログは以下のようになります。
APIエンドポイントのURL( /api/shopify/get-products
)が出力され、その後にserver/api/shopify/get-projects.ts
)が出力されています。
このようにServer Middlewareは、サーバーを介する全てのhttpリクエストに対して実行されます。
特定のパスでのみでMiddlewareを発火させる
serverMiddleware
プロパティ内で、特定のパスでMiddlewareを発火させるようにカスタマイズすることが可能です。
!! 特定のルートでServer Milldewareを発火させることができませんでした。
Nuxt Version:
3.0.0-27444434.856c01a
注:ミドルウェアをすべてのルートで実行したくない場合は、特定のパスを持つオブジェクトフォームを使用する必要があります。
と、ドキュメントには書いていましたがなぜか全てのルートで実行されます。
ちなみにNuxt2で同じコードを試したところ、ちゃんと指定したパスのみServer Middlewareが発火しました。
これはNuxt3の改善を待ちましょう。
nuxt.config.ts
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
serverMiddleware: [
{ path: '/api', handler: '~/server/middleware/api-logs' }
]
}
上記は、パスが/api
から始まるサーバーリクエストに対して、
しかし全てのルートでログが出力されているので、このコードが有効になっていません。
現在のNuxt3では、特定のルートでServer Middlewareを発火させるのは不可能のようです。
Server Middlewareの呼び出し順序
呼び出し順序を特に指定しない場合は、「middleware」ディレクトリの下から順に呼ばれます。
ファイル構成
server/middleware
├── api-logs.ts => 呼び出し③
├── api-logs2.ts => 呼び出し②
└── api-logs3.ts => 呼び出し①
terminalの出力
server/middleware/api-logs3.ts
2022/3/15 15:21:51: GET
2022/3/15 15:21:51: /
-----------------------------
server/middleware/api-logs2.ts
2022/3/15 15:21:51: GET
2022/3/15 15:21:51: /
-----------------------------
server/middleware/api-logs.ts
2022/3/15 15:21:51: GET
2022/3/15 15:21:51: /
-----------------------------
この呼出順序を指定したい場合はserverMiddleware
を使用します。
nuxt.config.ts
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
serverMiddleware: [
'~/server/middleware/api-logs',
'~/server/middleware/api-logs2',
'~/server/middleware/api-logs3'
]
}
serverMiddleware
に登録した順に呼ばれます。
terminalの出力
server/middleware/api-logs.ts
2022/3/15 15:21:51: GET
2022/3/15 15:21:51: /
-----------------------------
server/middleware/api-logs2.ts
2022/3/15 15:21:51: GET
2022/3/15 15:21:51: /
-----------------------------
server/middleware/api-logs3.ts
2022/3/15 15:21:51: GET
2022/3/15 15:21:51: /
-----------------------------
Server Middlewareのライフサイクル
Server Middlewareは、Nuxtサーバーが起動した直後に呼ばれます。
サーバーサイドで呼ばれるプラグインファイルより前に呼ばれるので、実質1番目に実行されます。
- Nuxt生成プロセス開始
- Nuxt フックの登録
- Server Middleware <
- Server-side Plugins
- nuxtServerInit
Server MiddlewareとServer-side Plugins
Server MiddlewareではuseNuxtApp
やリダイレクト処理を行うnavigateTo()
などは使用できません。
対してServer-side PluginsではuseNuxtApp
やnavigateTo()
を使用できます。
例えばログインしたままにする実装は、サイトにアクセスした最初の一度のみ、サーバーサイドで認証の有無を確認する処理が行われます。
このような「サイトにアクセスした最初の一度のみ、サーバーサイドで」の処理は、Server-side Pluginsで行う方が良いでしょう。
また、サーバーサイドのリダイレクト処理についても、navigateTo()
などを使用できるServer-side Pluginsを使用した方が簡単です。
下記目的によって使い分けると良いでしょう。
- サーバーリクエスト時に毎回処理を行いたい場合は => Server Middleware
- サイト訪問時のみ処理を行いたい場合は => Server-side Plugins
(おまけ)Server Middlewareでリダイレクト処理
下記は、Server Middlewareで全てのURLにクエリを付けてリダイレクトを行うコードです。
res.writeHead()
を使用し、URLにクエリがない場合にリダイレクト処理を行います。
server/middleware/redirect-checkout-query.ts
import type { IncomingMessage, ServerResponse } from 'http'
import Url from 'url'
export default async (req: IncomingMessage, res: ServerResponse, next: any) => {
console.log('server/middleware/redurect-checkout-query.ts')
const url = Url.parse(req.url, true)
const path = url.path
const checkoutId = url.query.checkoutId
/**
* リダイレクトを実行するには、リダイレクトステータスを送信します
* 永続的なリダイレクトの場合は301、「これは現在存在しています...」
* リダイレクトの場合は302、
* 意図的な一時的なリダイレクトの場合は307
* https://stackoverflow.com/questions/11355366/how-to-redirect-users-browser-url-to-a-different-page-in-nodejs
*/
if (!checkoutId) {
res.writeHead(307, {
Location: `${path}?checkoutId=aaaa`
})
return res.end()
}
next()
}
上記コードはAPIエンドポイントが呼ばれた場合に、意図せず複数回呼ばれる挙動を確認しました。
Nuxt3 beta版のうちは、まだServer Middlewareを使用しない方が賢明かもしれません。
完。