Cloudflare は、Pages プロジェクトで使える公式 Pages Plugins をいくつか提供しています。
- Cloudflare Access
- Google Chat
- GraphQL
- hCaptcha
- Honeycomb
- Sentry
- Static Forms
- Stytch
- Turnstile
- コミュニティプラグイン
- vercel/og
Pages Plugin は、組み込みのルーティングと機能を含む、配布可能な Pages Functions です。開発者は Pages プロジェクトの好きな場所に Plugin を含め、設定オプションを渡せます。ミドルウェア、パラメーター付きルート、静的アセットなど、Functions の機能をすべて Plugin で使えます。
たとえば、Pages Plugin では次のようなことができます。
- HTML ページを傍受し、サードパーティのスクリプトを挿入する。
- サードパーティサービスの API をプロキシする。
- 認可ヘッダーを検証する。
- 管理者向けの Web アプリ体験を一式提供する。
- KV または Durable Objects にデータを保存する。
- CMS のデータを使って Web ページをサーバーサイドレンダリング(SSR)する。
- エラーを報告し、パフォーマンスを追跡する。
Pages Plugin は、既存の Pages プロジェクトを Functions と深く統合して拡張するためのライブラリです。
開発者は、アプリケーションのルートに Pages Plugin をマウントしてプロジェクトを強化できます。Plugin は、通常どこにマウントすべきかの手順を提供します(例: 管理画面は functions/admin/[[path]].ts、エラーロガーは functions/_middleware.ts)。加えて、Plugin ごとに設定を受け取ることがあります(例: API トークン)。
この例では、Pages Plugin を作成し、プロジェクトに含めます。
最初の Plugin の役割は次のとおりです。
- HTML フォームを傍受する。
- フォーム送信を KV に保存する。
- 開発者が指定したレスポンスで送信に応答する。
次の内容で package.json を作成します。
{
"name": "@cloudflare/static-form-interceptor",
"main": "dist/index.js",
"types": "index.d.ts",
"files": ["dist", "index.d.ts", "tsconfig.json"],
"scripts": {
"build": "npx wrangler pages functions build --plugin --outdir=dist",
"prepare": "npm run build"
}
}この例では、dist/index.js が Plugin のエントリポイントになります。これは Wrangler が npm run build コマンドで生成するファイルです。dist/ ディレクトリを .gitignore に追加します。
次に、functions ディレクトリを作成し、Plugin の実装を始めます。functions フォルダーは、開発者によってあるルートにマウントされます。ファイル構成をどうするか検討してください。一般的には次のとおりです。
- Plugin を、開発者が選んだ単一ルート(例:
/foo)で動かしたい場合は、functions/index.tsを作成します。 - Plugin をマウントし、あるパス以降のすべてのリクエスト(例:
/admin/loginと/admin/dashboard)を処理したい場合は、functions/[[path]].tsを作成します。 - リクエストを傍受しつつ、ほかの Functions またはプロジェクトの静的アセットにフォールバックしたい場合は、
functions/_middleware.tsを作成します。
必要な数だけファイルを使えます。Plugin の構成は、現在の Pages プロジェクトの Functions とまったく同じです。違いは、ハンドラーがパラメーターオブジェクトの新しいプロパティ pluginArgs を受け取ることです。このプロパティは、開発者が Plugin をマウントするときに渡す初期化パラメーターです。API トークン、KV / Durable Object 名前空間など、Plugin の動作に必要なものを受け取れます。
静的フォームの例に戻ります。リクエストを傍受し、HTML フォームの挙動を上書きするには、functions/_middleware.ts を作成します。開発者は、単一ルートまたはプロジェクト全体に Plugin をマウントできます。
class FormHandler {
element(element) {
const name = element.getAttribute("data-static-form-name");
element.setAttribute("method", "POST");
element.removeAttribute("action");
element.append(
`<input type="hidden" name="static-form-name" value="${name}" />`,
{ html: true },
);
}
}
export const onRequestGet = async (context) => {
// We first get the original response from the project
const response = await context.next();
// Then, using HTMLRewriter, we transform `form` elements with a `data-static-form-name` attribute, to tell them to POST to the current page
return new HTMLRewriter()
.on("form[data-static-form-name]", new FormHandler())
.transform(response);
};
export const onRequestPost = async (context) => {
// Parse the form
const formData = await context.request.formData();
const name = formData.get("static-form-name");
const entries = Object.fromEntries(
[...formData.entries()].filter(([name]) => name !== "static-form-name"),
);
// Get the arguments given to the Plugin by the developer
const { kv, respondWith } = context.pluginArgs;
// Store form data in KV under key `form-name:YYYY-MM-DDTHH:MM:SSZ`
const key = `${name}:${new Date().toISOString()}`;
context.waitUntil(kv.put(name, JSON.stringify(entries)));
// Respond with whatever the developer wants
const response = await respondWith({ formData });
return response;
};開発者体験をよくするために、Plugin に TypeScript の型を付けることを検討してください。IDE のオートコンプリートが使え、想定するパラメーターを漏れなく含められるようになります。
index.d.ts で、pluginArgs を受け取り PagesFunction を返す関数をエクスポートします。静的フォームの例では、2 つのプロパティを受け取ります。kv は KV 名前空間、respondWith は formData プロパティ(FormData)を持つオブジェクトを受け取り、Response の Promise を返す関数です。
export type PluginArgs = {
kv: KVNamespace;
respondWith: (args: { formData: FormData }) => Promise<Response>;
};
export default function (args: PluginArgs): PagesFunction;Pages Plugin 作者向けのテスト体験は、まだ整備中です。揃うまでしばらくお待ちください。当面は、サンプルプロジェクトを作成し、テスト用に Plugin を手動で含めてください。
Plugin の配布方法は自由です。よくある選択肢は、npm ↗ での公開、Developer Discord ↗ の #what-i-built または #pages-discussions チャンネルでの紹介、GitHub ↗ でのオープンソース化です。
生成された dist/ ディレクトリ、型定義の index.d.ts、開発者向け手順を書いた README.md を含めてください。
アプリケーションに Pages Plugin を含めるには、まずその Plugin をプロジェクトへインストールします。
プロジェクトでまだ npm を使っていない場合は、npm init を実行して package.json ファイルを作成します。Plugin の README.md には、通常インストールコマンドが載っています(例: npm install --save @cloudflare/static-form-interceptor)。
Plugin の README.md には、アプリケーションへのマウント方法が載っていることが多いです。次を実施します。
- まだない場合は、
functionsディレクトリを作成します。 - この Plugin を動かす場所を決め、
functionsディレクトリに対応するファイルを作成します。 - このファイルで Plugin をインポートし、必要な引数で初期化したうえで、
onRequestメソッドをエクスポートします。
静的フォームの例では、作成した Plugin はミドルウェアです。単一ルートでも、プロジェクト全体でも動かせます。サイトの /contact に問い合わせフォームが 1 つだけある場合は、functions/contact.ts を作成してそのルートだけを傍受できます。functions/_middleware.ts を作成して、ほかのルートと将来追加するフォームも傍受することもできます。この Plugin をどこで動かすかは、開発者が選べます。
Plugin のデフォルトエクスポートは、通常の Pages Functions ハンドラーと同じコンテキストパラメーターを受け取る関数です。
import staticFormInterceptorPlugin from "@cloudflare/static-form-interceptor";
export const onRequest = (context) => {
return staticFormInterceptorPlugin({
kv: context.env.FORM_KV,
respondWith: async ({ formData }) => {
// Could call email/notification service here
const name = formData.get("name");
return new Response(`Thank you for your submission, ${name}!`);
},
})(context);
};wrangler pages dev を使うと、インストールした Plugin を含む Pages プロジェクトをテストできます。Plugin が必要とする KV バインディングと環境変数を忘れずに含めてください。
Plugin を /contact ルートにマウントした場合、対応する HTML ファイルは次のようになります。
<!DOCTYPE html>
<html>
<body>
<h1>Contact us</h1>
<!-- Include the `data-static-form-name` attribute to name the submission -->
<form data-static-form-name="contact">
<label>
<span>Name</span>
<input type="text" autocomplete="name" name="name" />
</label>
<label>
<span>Message</span>
<textarea name="message"></textarea>
</label>
</form>
</body>
</html>Plugin は data-static-form-name="contact" 属性を検出し、method="POST" を設定し、<input type="hidden" name="static-form-name" value="contact" /> 要素を挿入し、POST 送信をキャプチャします。
新しい Plugin が package.json に追加され、ローカルで想定どおり動くことを確認します。そのあと git commit と git push を実行し、Cloudflare Pages のデプロイを開始します。
特定の Plugin で問題が起きた場合は、その Plugin のバグトラッカーに issue を登録してください。
Plugin 全般で問題が起きた場合は、Discord ↗ の #pages-discussions チャンネルへフィードバックをお願いします。Plugin で作られるもの、および作者体験や開発者体験へのフィードバックを歓迎します。Plugin をさらに強力にするために必要なことがあれば、Discord チャンネルで知らせてください。
最後に、Pages Functions 全般と同様、Plugin をチェーンして機能を組み合わせられます。ファイルシステム上でより上位に定義したミドルウェアは、ほかのハンドラーより先に実行されます。個別のファイルでは、次のように配列で Functions をチェーンできます。
import sentryPlugin from "@cloudflare/pages-plugin-sentry";
import cloudflareAccessPlugin from "@cloudflare/pages-plugin-cloudflare-access";
import adminDashboardPlugin from "@cloudflare/a-fictional-admin-plugin";
export const onRequest = [
// Initialize a Sentry Plugin to capture any errors
sentryPlugin({ dsn: "https://sentry.io/welcome/xyz" }),
// Initialize a Cloudflare Access Plugin to ensure only administrators can access this protected route
cloudflareAccessPlugin({
domain: "https://test.cloudflareaccess.com",
aud: "4714c1358e65fe4b408ad6d432a5f878f08194bdb4752441fd56faefa9b2b6f2",
}),
// Populate the Sentry plugin with additional information about the current user
(context) => {
const email =
context.data.cloudflareAccessJWT.payload?.email || "service user";
context.data.sentry.setUser({ email });
return next();
},
// Finally, serve the admin dashboard plugin, knowing that errors will be captured and that every incoming request has been authenticated
adminDashboardPlugin(),
];