Nuxt3betaのServerMiddlewareで特定ルートのみ発火させる方法は動かない
  • 2022.03.15に公開
  • Nuxt3
  • 0. Nuxt3
  • No.6 / 9
現在このカテゴリーは、Nuxt3パブリックベータ版の情報を公開しています。変更の可能性が大きくあるので、最新の情報は Nuxt3の公式サイト をご確認ください。

記事タイトルの内容はこちら

Nuxt3のServer Middleware

Nuxt3のServer Middlewareは、サーバーリクエストの際に実行したい処理を記述する場所です。

「server/middleware」以下に置かれたファイルは自動で読み込まれ実行されます。

Server Middlewareの使い道

注意してほしいのは、全てのサーバーリクエストで実行される点です。

これはサーバーを介したベージ遷移の際にも、useFetchでAPIエンドポイントを呼び出す際にも実行されます。

公式ドキュメントにあるように、「server/api」ディレクトリ以下のAPIを呼び出す際に、何らかの共通処理を実行したい場合などに活用できそうです。

「server/middleware」のファイルは、独自のルートにマッピングされるAPIルートとは異なり、すべてのリクエストで実行されます。

これは通常、すべてのレスポンスに共通のヘッダを追加したり、レスポンスをログに記録したり、リクエストチェーンで後で使用するために受信リクエストオブジェクトを変更したりできるようにするためのものです。

引用: Nuxt 3 - Server directory

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を呼ぶことを忘れないでください。

引用: Nuxt 3 - Nuxt configuration file

ログを確認する

トップページに移動して、ページをリロードしターミナルを確認してみましょう。

2022-03-15 13-36-09

API呼び出しページのログ

パス/productsに遷移してみます。

/productsを構築するVueファイルには、useFetchを使用しサーバーサイドからAPIリクエストを投げています。

pages/products/index.vue
<script setup lang="ts">
  const { data: products } = await useFetch('/api/shopify/get-products')
</script>

get-products.tsでは、Shopifyへ商品一覧を取得するようAPIを投げています。

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のログは以下のようになります。

2022-03-15 14-30-40

APIエンドポイントのURL( /api/shopify/get-products)が出力され、その後にget-products.tsのログ(server/api/shopify/get-projects.ts)が出力されています。

このようにServer Middlewareは、サーバーを介する全てのhttpリクエストに対して実行されます。

特定のパスでのみでMiddlewareを発火させる

nuxt.config.tsserverMiddlewareプロパティ内で、特定のパスで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から始まるサーバーリクエストに対して、api-logs.tsを発火するコードです。

しかし全てのルートでログが出力されているので、このコードが有効になっていません。

現在の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: /
-----------------------------

この呼出順序を指定したい場合はnuxt.config.tsserverMiddlewareを使用します。

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番目に実行されます。

  1. Nuxt生成プロセス開始
  2. Nuxt フックの登録
  3. Server Middleware <
  4. Server-side Plugins
  5. nuxtServerInit

参考: Nuxt - Nuxt Lifecycle

Server MiddlewareとServer-side Plugins

Server MiddlewareではuseNuxtAppやリダイレクト処理を行うnavigateTo()などは使用できません。

対してServer-side PluginsではuseNuxtAppnavigateTo()を使用できます。

例えばログインしたままにする実装は、サイトにアクセスした最初の一度のみ、サーバーサイドで認証の有無を確認する処理が行われます。

このような「サイトにアクセスした最初の一度のみ、サーバーサイドで」の処理は、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を使用しない方が賢明かもしれません。

完。

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる。そんな方に向けた単発・短期間メンターサービスを行っています。
独学プログラマのサービス
独学プログラマ
独学でも、ここまでできるってよ。
CONTACT
Nuxt.js制作のご依頼は下記メールアドレスまでお送りください。