リクエストとCookieを読む
このモードでは、ページがリクエストのヘッダーとCookieを読めます。一方でCookieを書くのはページではなく、リクエストに答える側のguard.tsやroute.ts、Server Actionです。このページでは、読む方法と書く方法を順に説明します。
このページの内容
ページからリクエストを読む
ページとレイアウト、not-found.tsxは、paramsとpathnameの横にrequestを受け取ります。中身はリクエストのヘッダーと、名前ごとに読み取ったCookieです。
src/routes/layout.tsximport type { LayoutProps } from '@k8ordo/router';
export default function RootLayout({
children,
request,
}: LayoutProps<'/'>) {
const theme = request.cookies.get('theme') ?? 'light';
return (
<html data-theme={theme} lang="en">
<body>{children}</body>
</html>
);
}request.headers:リクエストのヘッダーです。型はHeadersです。request.cookies:Cookieヘッダーを名前ごとに読み取ったReadonlyMapです。同じ名前が2回あれば最初のものを使い、値を囲む引用符を外してURLデコードします。
PagePropsとLayoutPropsがrequestを持つのは、生成されたregister.gen.tsに、このモードにはリクエストがあると書かれているからです。下のコンポーネントにpropとして渡すときは、@k8ordo/server/runtimeのRouteRequestを型に使います。
src/routes/_parts/greeting.tsximport type { RouteRequest } from '@k8ordo/server/runtime';
export function Greeting({ request }: { request: RouteRequest }) {
const name = request.cookies.get('name') ?? 'there';
return <p>Hello, {name}</p>;
}クライアントコンポーネントにはrequestを丸ごと渡さず、要る値だけを取り出して渡します。Headersは、境界を越えられないからです。
requestにsearchは入りません。?から後ろは@k8ordo/stateのもので、ブラウザで読みます。
cookies()でCookieを読み書きする
guard.tsやroute.ts、Server Actionの中では、@k8ordo/server/runtimeのcookies()でCookieを読み書きできます。次の例は、ログインに成功したらセッションのCookieを書いて、アカウントのページへ送るアクションです。
src/routes/login/_parts/sign-in.ts'use server';
import { href } from '@k8ordo/router';
import { cookies, redirect } from '@k8ordo/server/runtime';
import { startSession } from '../../_data/sessions.server';
export type SignInState = { error?: string };
export async function signIn(
_previous: SignInState,
formData: FormData,
): Promise<SignInState> {
const session = await startSession(formData);
if (session === null) return { error: 'Wrong password' };
cookies().set('session', session.token, {
maxAge: 60 * 60 * 24 * 30,
});
redirect(href('/account'));
}読むと、リクエストが運んできたCookieに、同じリクエストの中で先に書いたものが重なって見えます。guardが書いた値は、その後に走るServer Actionが読めます。
書いたものは、答えが何であっても、その答えのSet-Cookieとしてブラウザに届きます。アクションが描き直したページでも、redirect()の303でも同じです。
値はSet-Cookieに載せるときにパーセントエンコードされ、リクエストで戻ってくるときにデコードされます。そのためsetにはどんな文字列もそのまま渡せます。エンコード済みの値を渡すと、二重にエンコードされます。
メモ
アクションの後に描き直すページがrequest.cookiesで見るのは、リクエストが運んできたCookieです。アクションが書いた値ではありません。
Cookieの属性を決める
setの3つ目の引数で、Cookieの属性を渡します。何も渡さなければ、セッションに合った値になります。
path:既定は/です。domain:既定では付けません。maxAge:有効期間を秒で渡します。0を渡すと、すぐに切れます。expires:期限をDateで渡します。httpOnly:既定はtrueで、ページのスクリプトからは読めません。secure:既定はtrueで、HTTPSでしか送られません。sameSite:'strict'か'lax'、'none'のどれかで、既定は'lax'です。'none'はsecureのときだけ使えます。
ただし、localhostと127.0.0.1、[::1]に素のHTTPで届いたリクエストだけは、secureの既定がfalseになります。ChromiumとFirefoxはそこでもSecureのCookieを残しますが、Safariは捨ててしまうからです。
それ以外の場所を素のHTTPで配信するなら、secure: falseを渡します。
deleteには、書いたときのpathとdomainを渡します。ブラウザはCookieを、この2つで見分けるからです。
Server Actionでリクエストのヘッダーを読む
Server Actionが受け取るのは引数で、リクエストではありません。アクションの中でヘッダーを読むときは、requestHeaders()を使います。
src/routes/_parts/report.ts'use server';
import { requestHeaders } from '@k8ordo/server/runtime';
import { saveReport } from '../_data/reports.server';
export async function report(formData: FormData): Promise<void> {
const agent = requestHeaders().get('user-agent') ?? 'unknown';
await saveReport(formData.get('body'), agent);
}requestHeaders()もcookies()と同じく、guard.tsやroute.ts、Server Actionの中でだけ使えます。guard.tsとroute.tsはrequestを受け取るので、ふつうはそちらを読みます。
ページが応答を書かない理由
ページには、ステータスやSet-Cookieを書く手段がありません。ページは描画であり、応答を書く描画は2つ目のハンドラになってしまうからです。
応答にページ以外の何を付けるかは、ページより前にguard.tsが決めるか、POSTが運んできたServer Actionが決めます。ページの中でcookies()やresponseHeaders()を呼ぶと、例外を投げます。
requestはこのモードにしかありません。@k8ordo/staticでは生成される型にrequestが無いので、それを読むページは型の検査で落ちます。