コンテンツにスキップ

ミドルウェア

ミドルウェアにより、リクエストとレスポンスをインターセプトし、ページやエンドポイントがレンダリングされる直前に動的に振る舞いを注入できます。

また、すべてのAstroコンポーネントとAPIエンドポイントで利用可能なlocalsオブジェクトを変更し、リクエスト固有の情報を各エンドポイントとページで設定・共有することもできます。

ミドルウェアはSSGとSSRのAstroプロジェクトの両方で利用可能です。

  1. src/middleware.js|ts というファイルを作成します。(あるいは、src/middleware/index.js|ts を作成しても構いません。)

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

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

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

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

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

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

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

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

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

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

src/pages/orders.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はもう存在せず、その後また新しいものが作成されます。複数のページリクエストをまたいで保持されるべき情報は、別の場所に保存する必要があります。

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

src/middleware.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()ユーティリティ関数をインポートして使用すると、型安全性を確保できます。

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

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

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

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

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

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

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

src/middleware.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);

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

Terminal window
validationリクエスト
authリクエスト
greetingリクエスト
greetingレスポンス
authレスポンス
validationレスポンス