import PackageManagerTabs from '/components/tabs/PackageManagerTabs.astro'; import { Steps } from '@astrojs/starlight/components'; import Since from '/components/Since.astro';

ミドルウェアにより、リクエストとレスポンスをインターセプトし、ページやエンドポイントがレンダリングされる直前に動的に振る舞いを注入できます。こうしたレンダリングは、ページが事前レンダリングされる場合にはビルド時におこなわれますが、オンデマンドにレンダリングされるページの場合はルートへのリクエスト時におこなわれ、クッキーやヘッダーなどの追加のSSR機能が利用できます。

また、ミドルウェアを使って、すべてのAstroコンポーネントとAPIエンドポイントで利用可能なlocalsオブジェクトを変更し、リクエスト固有の情報を各エンドポイントとページで設定・共有することもできます。このオブジェクトは、このミドルウェアがビルド時に実行される場合でも利用できます。

基本的な使い方

  1. このファイルの中で、contextオブジェクトnext()関数を受け取るonRequest()関数をエクスポートします。これをデフォルトエクスポートにしてはいけません。

    js
    export function onRequest (context, next) {    // リクエストからデータをインターセプトします    // 必要に応じて、`locals`内のプロパティを改変します    context.locals.title = "新しいタイトル";    // Responseか`next()`の結果を返します    return next();};
  2. .astroファイルの中で、Astro.localsを使ってレスポンスデータにアクセスします。

    astro
    ---const data = Astro.locals;---<h1>{data.title}</h1><p>この{data.property}はミドルウェアで設定しました。</p>

contextオブジェクト

contextオブジェクトには、レンダリング中に他のミドルウェア、APIルート、.astroルートで利用可能な情報が含まれています。

これはonRequest()に渡されるオプション引数で、localsオブジェクトや、レンダリング中に共有されるその他のプロパティを含む場合があります。たとえばcontextオブジェクトには、認証に使用されるクッキーを含められます。

context.localsにデータを保存する

context.localsは、ミドルウェア内で変更可能なオブジェクトです。

このlocalsオブジェクトは、リクエスト処理のプロセスを通じて受け渡されていき、APIContextAstroGlobalのプロパティとして利用できます。これにより、ミドルウェア、APIルート、.astroページ間でデータを共有できます。ユーザーデータなど、リクエスト固有のデータを各レンダリングステップをまたいで保持する際に役立ちます。

:::tip[インテグレーションのプロパティ] インテグレーションは、localsオブジェクトを介してプロパティを設定し、機能を提供する場合があります。インテグレーションを使用している場合は、そのドキュメントを確認して、プロパティを上書きしていないか、不要な操作をしていないかを確認してください。 :::

localsには、文字列、数値、さらには関数やマップといった複雑なデータ型など、どんな型のデータでも格納できます。

js
export function onRequest (context, next) {    // リクエストからデータをインターセプトします    // 必要に応じて、`locals`内のプロパティを改変します    context.locals.user.name = "John Wick";    context.locals.welcomeTitle = () => {        return "おかえりなさい " + locals.user.name;    };    // Responseか`next()`の結果を返します    return next();};

そして、任意の.astroファイル内でAstro.localsによりこの情報を利用できます。

astro
---const title = Astro.locals.welcomeTitle();const orders = Array.from(Astro.locals.orders.entries());---<h1>{title}</h1><p>この{data.property}はミドルウェアで設定しました。</p><ul>    {orders.map(order => {        return <li>{/* 各値を使って何かします */}</li>;    })}</ul>

localsは単一のAstroルートの中で生成・消滅します。ページルートがレンダリングされると、localsはもう存在せず、その後また新しいものが作成されます。複数のページリクエストをまたいで保持されるべき情報は、別の場所に保存する必要があります。

:::note localsの値はランタイムに上書きできません。これをおこなうと、ユーザーが保存した情報がすべて消去される可能性があるためです。devモードにおいてAstroはこれを監視し、localsが上書きされた場合にエラーをスローします。 :::

センシティブな情報を消去する例

以下の例では、ミドルウェアを使用して「極秘情報」という文字列を「削除済み」という語に置き換えることで、変更されたHTMLをページにレンダリングできるようにします。

js
export const onRequest = async (context, next) => {    const response = await next();    const html = await response.text();    const redactedHtml = html.replaceAll("極秘情報", "削除済み");        return new Response(redactedHtml, {        status: 200,        headers: response.headers    });};

ミドルウェアの型

defineMiddleware()ユーティリティ関数をインポートして使用すると、型安全性を確保できます。

ts
// src/middleware.tsimport { defineMiddleware } from "astro:middleware";// `context`と`next`は自動的に型付けされますexport const onRequest = defineMiddleware((context, next) => {});

JsDocにより型を記述している場合は、MiddlewareHandlerを使用できます。

js
// src/middleware.js/** * @type {import("astro").MiddlewareHandler} */// `context`と`next`は自動的に型付けされますexport const onRequest = (context, next) => {};

Astro.locals内の情報に型を付け、.astroファイルとミドルウェアの両コードで自動補完を有効化するには、env.d.tsファイルでグローバル名前空間を宣言します。

ts
/// <reference path="../.astro/types.d.ts" />declare namespace App {    interface Locals {        user: {            name: string        },        welcomeTitle: () => string,        orders: Map<string, object>    }}

これにより、ミドルウェアファイル内で自動補完が有効になり、型安全性が確保されます。

ミドルウェアを連結する

sequence()を使用して、複数のミドルウェアを指定した順序で連結できます。

js
import { sequence } from "astro:middleware";async function validation(_, next) {    console.log("validationリクエスト");    const response = await next();    console.log("validationレスポンス");    return response;}async function auth(_, next) {    console.log("authリクエスト");    const response = await next();    console.log("authレスポンス");    return response;}async function greeting(_, next) {    console.log("greetingリクエスト");    const response = await next();    console.log("greetingレスポンス");    return response;}export const onRequest = sequence(validation, auth, greeting);

これにより、以下の順序でコンソールに出力されます。

sh
validationリクエストauthリクエストgreetingリクエストgreetingレスポンスauthレスポンスvalidationレスポンス

リライト

{/* TODO: routing.mdx の修正時に #rewrites を追加する */} APIContextは、Astro.rewriteと同じように動作するrewrite()メソッドを公開しています。

ページ閲覧者を新しいページにリダイレクトすることなく異なるページコンテンツを表示するには、ミドルウェア内でcontext.rewrite()を使用します。これにより新しいレンダリングフェーズがトリガーされ、ミドルウェアが再実行されます。

js
import { isLoggedIn } from "~/auth.js"export function onRequest (context, next) {  if (!isLoggedIn(context)) {    // ユーザーがログインしていない場合、`/login`ルートをレンダリングするようにRequestを更新し、    // ログイン成功後にユーザーをどこに送り返すかを示すヘッダーを追加します。    // ミドルウェアは再実行されます。    return context.rewrite(new Request("/login", {      headers: {        "x-redirect-to": context.url.pathname      }    }));  }  return next();};

また、next()関数にオプションのURLパスパラメータを渡すことで、新しいレンダリングフェーズを再トリガーすることなく、現在のRequestを書き換えることができます。リライト先のパスは、文字列、URL、またはRequestとして与えます。

js
import { isLoggedIn } from "~/auth.js"export function onRequest (context, next) {  if (!isLoggedIn(context)) {    // ユーザーがログインしていない場合、`/login`ルートをレンダリングするようにRequestを更新し、    // ログイン成功後にユーザーをどこに送り返すかを示すヘッダーを追加します。    // 後続のミドルウェアに新しい`context`を返します。    return next(new Request("/login", {      headers: {        "x-redirect-to": context.url.pathname      }    }));  }  return next();};

next()関数には、Astro.rewrite()関数と同じペイロードを渡せます。リライト先のパスは、文字列、URL、またはRequestとして与えます。sequence()により複数のミドルウェア関数を連結している場合、next()にパスを渡すとRequestはその場で書き換えられ、ミドルウェアは再実行されません。チェーン内の次のミドルウェア関数は、更新されたcontextをもつ新しいRequestを受け取ります。

js
// 現在のURLは https://example.com/blog です// 最初のミドルウェア関数async function first(_, next) {  console.log(context.url.pathname) // これは"/blog"をログに出力します  // 新しいルートであるホームページにリライトします  // 次の関数に渡される、更新された`context`を返します  return next("/")}// 現在のURLはまだ https://example.com/blog です// 2番目のミドルウェア関数async function second(context, next) {  // 更新された`context`を受け取ります  console.log(context.url.pathname) // これは"/"をログに出力します  return next()}export const onRequest = sequence(first, second);

エラーページ

ミドルウェアは、オンデマンドレンダリングされるすべてのページに対して実行を試みます。これにはAstroのデフォルト(空白)の404ページや、カスタムの404ページが含まれます。ただし、そのコードが実行されるかどうかはアダプターによって決定されます。一部のアダプターは、プラットフォーム固有のエラーページを代わりに提供する場合があります。

ミドルウェアはまた、カスタムの500ページを含む500エラーページを提供する前にも実行を試みます。ただし、ミドルウェア自体の実行中にサーバーエラーが発生した場合を除きます。ミドルウェアが正常に実行されない場合、500ページをレンダリングするためにAstro.localsにアクセスすることはできません。

{/* TODO: このセクションはAPIリファレンスに移動する

APIリファレンス

onRequest()

src/middleware.jsからエクスポートされる必須の関数で、各ページやAPIルートのレンダリングの前に呼び出されます。2つのオプション引数、contextnext()を受け取ります。onRequest()は、Responseを返す必要があります。直接、またはnext()を呼び出してください。

context

レンダリング処理中に、他のミドルウェア、APIルート、.astroルートで利用可能な情報を含むオブジェクトです。

これはonRequest()に渡されるオプション引数で、localsオブジェクトや、レンダリング中に共有されるその他のプロパティを含む場合があります。たとえばcontextオブジェクトには、認証に使用されるクッキーを含められます。

これはAPIルートに渡されるcontextオブジェクトと同じものです。

next()

RequestResponseをインターセプトする(読み取り、変更を加える)か、ミドルウェアのチェーン内の「次の」ミドルウェアを呼び出してResponseを返すための関数です。たとえばこの関数を用いて、レスポンスのHTML本文を変更できます。

これはonRequest()のオプション引数で、ミドルウェアが返す必要のあるResponseを提供します。

locals

ミドルウェア内で変更可能な、Responseからのデータを含むオブジェクトです。

このlocalsオブジェクトは、リクエスト処理のプロセスを通じて転送されていき、APIContextAstroGlobalのプロパティとして利用できます。これにより、ミドルウェア、APIルート、.astroページ間でデータを共有できます。ユーザーデータなど、リクエスト固有のデータを各レンダリングステップをまたいで保持するために役立ちます。

localsは単一のAstroルーティング内で生成・消滅します。ルーティングされたページがレンダリングされると、localsはもう存在せず、その後また新しいものが作成されます。複数のページリクエストをまたいで保持されるべき情報は、別の場所に保存する必要があります。

:::note localsの値は実行時に上書きできません。それをした場合、ユーザーが保存した情報がすべて消去される可能性があるためです。devモードにおいてAstroはこれを監視し、localsが上書きされた場合にエラーをスローします。 :::

sequence()

ミドルウェア関数を引数として受け取り、渡された順序で実行する関数です。

createContext

Astroミドルウェアで使用可能なAPIContextを作成するための低レベルなAPIです。

この関数は、Astroミドルウェアをプログラムから実行する目的で、インテグレーションとアダプターから使用できます。

trySerializeLocals

任意の値を受け取り、それをシリアライズした文字列を返す低レベルなAPIです。値をシリアライズできない場合、この関数はランタイムエラーをスローします。 */}