FeaturesEdit this page.md

HTTP コンテクスト

@lazarv/react-server を使用すると、サーバーサイドレンダリングのコンテクストに関連するすべてにアクセスできます。このガイドでは、HTTPコンテクストにアクセスする方法について説明します。

これから紹介するものは、HTTPコンテクストにアクセスして操作するためのフックまたは関数です。

これらの関数はすべて、ミドルウェアやファイルシステムベースのルーターを使用したルートハンドラーでも利用可能です。これらはランタイム固有であり、Reactとは関係ありません。

useHttpContext() を使用すると、完全なHTTPコンテクストにアクセスできます。

import { useHttpContext } from "@lazarv/react-server"; export default function MyComponent() { const context = useHttpContext(); return ( <div> <p>Method: {context.request.method}</p> <p>URL: {context.request.url.toString()}</p> <p>Request Headers: {JSON.stringify(context.request.headers)}</p> <p>Request Cookies: {JSON.stringify(context.request.cookie)}</p> </div> ); };

useRequest() を使用すると、完全なHTTPリクエストにアクセスできます。

import { useRequest } from "@lazarv/react-server"; export default function MyComponent() { const request = useRequest(); return ( <div> <p>Method: {request.method}</p> <p>URL: {request.url.toString()}</p> <p>Headers: {JSON.stringify(request.headers)}</p> <p>Cookies: {JSON.stringify(request.cookie)}</p> </div> ); };

useResponse() を使用すると、完全なHTTPレスポンスにアクセスできます。これは、レスポンスがクライアントに送信された後、サスペンドされ、後でクライアントにストリーミングされたReactコンポーネント内でのみ利用可能です。

import { useResponse } from "@lazarv/react-server"; export default async function MyComponent() { const response = await useResponse(); return ( <div> <p>Headers: {JSON.stringify(response.headers)}</p> <p>Cookies: {JSON.stringify(response.cookie)}</p> </div> ); };

useUrl() を使用すると、現在のリクエストのURLにアクセスできます。

import { useUrl } from "@lazarv/react-server"; export default function MyComponent() { const url = useUrl(); return <p>URL: {url.href}</p>; };

usePathname() を使用すると、現在のリクエストのパス名にアクセスできます。

import { usePathname } from "@lazarv/react-server"; export default function MyComponent() { const pathname = usePathname(); return <p>Pathname: {pathname}</p>; };

useSearchParams() を使用すると、現在のリクエストの検索パラメータにアクセスできます。これは、検索パラメータのキーと値のペアを持つオブジェクトです。同じキーに複数の値がある場合、その値は配列になります。

import { useSearchParams } from "@lazarv/react-server"; export default function MyComponent() { const searchParams = useSearchParams(); return ( <p>Search params: {JSON.stringify(searchParams)}</p> ); };

headers() を使用すると、現在のリクエストのヘッダーにアクセスできます。

import { headers } from "@lazarv/react-server"; export default function MyComponent() { const requestHeaders = headers(); return <p>Headers: {JSON.stringify(requestHeaders)}</p>; };

キーと値のペアのオブジェクトを渡すことで、現在のレスポンスのヘッダーを変更することもできます。

import { headers } from "@lazarv/react-server"; export default function MyComponent() { headers({ "X-My-Header": "My value", }); return <p>Headers: {JSON.stringify(headers())}</p>; };

または、Headers オブジェクトを渡すことでも変更できます。

import { headers } from "@lazarv/react-server"; export default function MyComponent() { headers(new Headers({ "X-My-Header": "My value", })); return <p>Headers: {JSON.stringify(headers())}</p>; };

または、キーと値のペアの配列を渡すことでも変更できます。

import { headers } from "@lazarv/react-server"; export default function MyComponent() { headers([ ["X-My-Header", "My value"], ]); return <p>Headers: {JSON.stringify(headers())}</p>; };

headers() 関数を使用してヘッダーを変更すると、現在のレスポンスのヘッダーが上書きされます。レスポンスヘッダーを直接変更したい場合は、ヘッダーを設定、追加、削除するための3つの補助関数を使用できます。これらの関数は setHeader()appendHeader()、および deleteHeader() です。

import { setHeader, appendHeader, deleteHeader } from "@lazarv/react-server"; export default function MyComponent() { setHeader("X-My-Header", "My first value"); appendHeader("X-My-Header", "My second value"); deleteHeader("X-My-Header"); return <p>Check the response headers!</p>; }

注意: HTTPヘッダーは大文字と小文字を区別しないことに注意してください!

cookie() を使用すると、現在のリクエストのクッキーにアクセスできます。

import { cookie } from "@lazarv/react-server"; export default function MyComponent() { const requestCookies = cookie(); return <p>Cookies: {JSON.stringify(requestCookies)}</p>; };

また、現在のレスポンスのコンテクストでクッキーを設定または削除することもできます。

import { setCookie, deleteCookie } from "@lazarv/react-server"; export default function MyComponent() { setCookie("my-cookie", "my-value"); deleteCookie("other-cookie"); return <p>Cookies: {JSON.stringify(cookie())}</p>; };

status() を使用すると、現在のレスポンスのステータスコードとテキストを設定できます。

import { status } from "@lazarv/react-server"; export default function MyComponent() { status(404, "Not found"); return <p>Not Found</p>; };

useFormData() を使用すると、現在のリクエストのフォームデータにアクセスできます。

import { useFormData } from "@lazarv/react-server"; export default function MyComponent() { const formData = useFormData(); return ( <p>Form data: {JSON.stringify(Object.fromEntries(formData.entries()))}</p> ); };

redirect() を使用すると、現在のリクエストを別のURLにリダイレクトできます。

警告: redirect() 関数はエラーをスローし、ランタイムがそれをキャッチしてリダイレクトを実行します。try/catch ブロック内で redirect() を使用する場合、リダイレクトエラーであれば再スローすることを確認してください。

import { redirect } from "@lazarv/react-server"; export default function MyComponent() { redirect("https://example.com"); };

redirect() 関数は、クライアントでのリダイレクトの動作を制御するオプションの第3引数 kind を受け付けます。利用可能な種類は以下の通りです:

種類説明
"navigate"(デフォルト) replaceState を使用したRSCナビゲーションを実行します。ブラウザのURLは変更されますが、履歴エントリは追加されません。
"push"pushState を使用したRSCナビゲーションを実行します。ブラウザのURLが変更され、新しい履歴エントリが追加されるため、ユーザーは戻るボタンで戻ることができます。
"location"location.href を使用した完全なブラウザナビゲーションを強制します。外部URLへのリダイレクトやページの完全なリロードが必要な場合に便利です。
"error"ナビゲーションの代わりにクライアントでリダイレクトエラーをスローします。サーバーアクション呼び出しで try/catch によるカスタム処理が可能になります。
import { redirect } from "@lazarv/react-server"; // pushStateを使用したRSCナビゲーション(履歴エントリを追加) redirect("/dashboard", 302, "push"); // 完全なブラウザナビゲーション redirect("/oauth/authorize", 302, "location"); // カスタム処理のためにクライアントでスロー redirect("/login", 302, "error");

サーバーアクションで "error" 種類を使用する場合、クライアントでリダイレクトエラーをキャッチして処理できます:

"use client"; import { myServerAction } from "./actions"; export function MyComponent() { const handleClick = async () => { try { await myServerAction(); } catch (e) { if (e?.digest?.startsWith("Location=")) { const url = e.digest.split("Location=")[1]?.split(";")[0]; console.log(`リダイレクト先: ${url}`); } } }; return <button onClick={handleClick}>送信</button>; }

rewrite() を使用すると、現在のリクエストを別のURLにリライトできます。これは、ミドルウェア関数内で現在のリクエストのURLパス名を変更するのに便利です。

import { rewrite, useUrl } from "@lazarv/react-server"; export function init$() { return async () => { const { pathname } = useUrl(); if (pathname === "/old-pathname") { rewrite("/new-pathname"); } }; } export default function MyComponent() { const { pathname } = useUrl(); return <p>Current pathname: {pathname}</p>; }

useOutlet() を使用すると、現在のリクエストのアウトレットにアクセスできます。これは、現在のリクエストがレンダリングされているアウトレットの名前を取得するのに便利です。

import { useOutlet } from "@lazarv/react-server"; export default function MyComponent() { const outlet = useOutlet(); return <p>Outlet: {outlet}</p>; }

useRender() を使用すると、現在のリクエストのレンダーロックにアクセスできます。これは、非同期関数が実行されている間、またはロックが解除されるまで、React Server Componentのレンダリングをロックしたい場合に便利です。なぜならReact Server Componentはデフォルトでストリーミングを使用してレンダリングされるからです。特にHTTPヘッダーやクッキーを非同期のReact Server Componentで処理する場合に役立ちます。レンダリングをロックしないと、非同期関数が終了する前にヘッダーやクッキーがクライアントに送信されてしまいます。レンダリングプロセスでロックが検出されると、レンダリングはロックが解除されるまで待機し、ヘッダーやクッキーの送信を開始してからReact Server Componentのストリーミングを開始します。

import { headers, useRender } from "@lazarv/react-server"; export default function MyComponent() { const { lock } = useRender(); await lock(async () => { // Do something async await new Promise((resolve) => setTimeout(resolve, 1000)); headers({ "x-lock": "works", }); }); return <p>Render lock</p>; }

lock() 関数を使用して、後でロックを解除するための unlock() 関数を取得することもできます。

import { headers, useRender } from "@lazarv/react-server"; export default function MyComponent() { const { lock } = useRender(); const unlock = lock(); // Do something async await new Promise((resolve) => setTimeout(resolve, 1000)); headers({ "x-lock": "works", }); unlock(); return <p>Render lock</p>; }

logger を使用すると、ランタイムの組み込みロガーを使ってメッセージをログに記録できます。logger オブジェクトは infowarnerrordebug メソッドを提供し、ランタイムのロギングシステムと統合されて、一貫したフォーマットの出力を提供します。

import { logger } from "@lazarv/react-server"; export default function MyComponent() { logger.info("Rendering MyComponent"); return <p>Hello World</p>; }

logger は開発モードではランタイムのVite統合ロガーを自動的に使用してきれいにフォーマットされた出力を提供し、プロダクションでは console にフォールバックします。コンテキストを認識するため、after() コールバック内で呼び出された場合、ログ出力に (after) ラベルが付加され、レスポンス後のログとレンダリングログを区別できます。

import { after, logger } from "@lazarv/react-server"; export default function MyComponent() { logger.info("Rendering component"); after(() => { logger.info("Response sent"); // 開発モードでは (after) ラベル付きでログ出力 }); return <p>Hello World</p>; }

利用可能なメソッド:

メソッド説明
logger.info(msg, ...args)情報メッセージをログに記録
logger.warn(msg, ...args)警告メッセージをログに記録
logger.error(msg, ...args)エラーメッセージまたは Error オブジェクトをログに記録
logger.debug(msg, ...args)デバッグメッセージをログに記録

Note: logger はサーバー上のどこでも使用できます — コンポーネント、サーバー関数、ミドルウェア、ルートハンドラ、ワーカー、after() コールバック内で利用可能です。リクエストコンテキストは必須ではありませんが、利用可能な場合はコンテキスト固有のロガーインスタンスを使用します。

after() を使用すると、レスポンスがクライアントに送信された後に実行されるコールバック関数を登録できます。これは、クリーンアップタスク、ロギング、アナリティクス、またはレスポンスを遅延させるべきではない副作用を実行するのに便利です。

import { after, logger } from "@lazarv/react-server"; export default function MyComponent() { after(() => { logger.info("Response sent to client."); }); return <p>Hello World</p>; }

after() フックは複数回呼び出して複数のコールバックを登録できます。登録されたすべてのコールバックは、レスポンスストリームが完了した後に Promise.allSettled を介して並行して実行されるため、1つのコールバックが失敗しても他のコールバックの実行は妨げられません。リクエストがエラーで失敗した場合、エラーは最初の引数として各コールバックに渡されます:

import { after, logger } from "@lazarv/react-server"; export default function MyComponent() { after((error) => { if (error) { logger.error("Request failed:", error.message); } else { logger.info("Request completed successfully"); } }); return <p>Hello World</p>; }
import { after } from "@lazarv/react-server"; export default function MyComponent() { after(async () => { await saveAnalytics({ page: "/home", timestamp: Date.now() }); }); after(async () => { await cleanupTempFiles(); }); return <p>Home</p>; }

サーバー関数、ミドルウェア、ルートハンドラ、またはリクエストコンテキスト内で実行されるサーバーサイドコードでも after() を使用できます:

import { after } from "@lazarv/react-server"; export async function submitForm(formData) { "use server"; const data = Object.fromEntries(formData.entries()); await saveToDatabase(data); after(async () => { await sendNotificationEmail(data.email); }); }

Note: after() フックはリクエスト中にのみ呼び出すことができます。リクエストコンテキスト外(モジュールスコープやスタンドアロンスクリプトなど)で呼び出すとエラーがスローされます。