Next.js の App Router では、サーバー側で動くコンポーネントから、ブラウザが送ってきた Cookie やリクエストヘッダーを直接読み取れます。そのために用意されているのが next/headers の cookies() と headers() です。ログイン状態を判定して表示を出し分けたり、User-Agent を見て処理を変えたりと、サーバーコンポーネントらしい使い方ができます。この記事では、cookies() と headers() の基本的な使い方から、Next.js 15 以降で必要になった await、Cookie の読み書きができる場所の違い、動的レンダリングになる点、そしてつまずきやすいポイントまでを、具体的なコード例とあわせて解説します。
目次
next/headers でできること
next/headers は App Router 専用のモジュールで、サーバー側からリクエストの情報を読み取るための関数を提供します。中心になるのが cookies() と headers() の2つです。cookies() はブラウザが送ってきた Cookie を読み書きするためのオブジェクトを返し、headers() はリクエストヘッダー(User-Agent や Referer など)を読み取るためのオブジェクトを返します。
これらは Server Component・Server Action・Route Handler といったサーバー側で動く場所でだけ使えます。'use client' を付けたクライアントコンポーネントでは使えないので注意してください。ブラウザ側で Cookie を触りたい場合は、従来どおり document.cookie やライブラリを使うことになります。
Next.js 15 以降は await が必要になった
最初に押さえておきたいのが、Next.js 15 から cookies() と headers() が非同期(async)関数になったという変更点です。以前は同期的に呼び出せましたが、現在は Promise を返すため、await を付けて解決する必要があります。呼び出す関数自体も async にしておきます。
import { cookies, headers } from 'next/headers';
export default async function Page() {
// await を付けて解決する(Next.js 15 以降)
const cookieStore = await cookies();
const headersList = await headers();
const theme = cookieStore.get('theme');
const userAgent = headersList.get('user-agent');
return (
<div>
<p>テーマ設定: {theme?.value ?? '未設定'}</p>
<p>User-Agent: {userAgent}</p>
</div>
);
}
もし await を付け忘れると、返ってくるのは Cookie オブジェクトではなく Promise なので、cookieStore.get が関数として存在せずエラーになります。App Router で cookies() や headers() を使うときは、必ず await を付けると覚えておくと安全です。
cookies() で Cookie を読み取る
await cookies() で得られるオブジェクト(ここでは cookieStore とします)には、Cookie を読み取るためのメソッドが用意されています。よく使うのは、名前を指定して1つ取得する get()、すべての Cookie を配列で取得する getAll()、存在するかどうかを確認する has() の3つです。
import { cookies } from 'next/headers';
export default async function Page() {
const cookieStore = await cookies();
// 1件取得(見つからないと undefined)
const token = cookieStore.get('token');
console.log(token?.name); // 'token'
console.log(token?.value); // Cookie の値
// すべて取得({ name, value } の配列)
const all = cookieStore.getAll();
// 存在チェック
const hasToken = cookieStore.has('token'); // true / false
return <p>Cookie の数: {all.length}</p>;
}
get() が返すのは { name, value } という形のオブジェクトで、Cookie が存在しない場合は undefined になります。そのため、上の例のように token?.value とオプショナルチェーンで安全に値を取り出すのがおすすめです。
Cookie を書き込める場所と読み取り専用の場所
Cookie の読み取りはどのサーバー環境でもできますが、書き込み(set() と delete())は場所が限られます。書き込みができるのは Server Action と Route Handler の中だけで、通常の Server Component の中では読み取り専用です。これは、Server Component がページの描画中に呼ばれるのに対し、Cookie の変更はレスポンスヘッダーへの書き込みが必要で、描画のタイミングでは間に合わないためです。
次は Server Action の中でログイントークンをセットし、ログアウト用に削除する例です。フォーム送信をきっかけに Cookie を書き換えます。
'use server';
import { cookies } from 'next/headers';
// ログイン: Cookie をセットする
export async function login() {
const cookieStore = await cookies();
cookieStore.set('token', 'abc123', {
httpOnly: true, // JS から読めないようにする
secure: true, // HTTPS のみ
path: '/',
maxAge: 60 * 60 * 24, // 1日
});
}
// ログアウト: Cookie を削除する
export async function logout() {
const cookieStore = await cookies();
cookieStore.delete('token');
}
もし通常の Server Component の中で cookieStore.set() を呼ぶと、「Cookies can only be modified in a Server Action or Route Handler(Cookie は Server Action か Route Handler でしか変更できない)」という趣旨のエラーになります。読み取りは Server Component で、書き込みは Server Action か Route Handler で、という役割分担を意識してください。
headers() でリクエストヘッダーを読む
headers() は、ブラウザが送ってきたリクエストヘッダーを読み取るための関数です。await headers() で得られるオブジェクトは Web 標準の Headers と同じインターフェースを持ち、get() でヘッダーの値を取得できます。こちらは読み取り専用で、リクエストヘッダーを書き換えることはできません。
import { headers } from 'next/headers';
export default async function Page() {
const headersList = await headers();
const userAgent = headersList.get('user-agent');
const referer = headersList.get('referer');
const acceptLang = headersList.get('accept-language');
// スマホからのアクセスか簡易判定する例
const isMobile = /Mobile/.test(userAgent ?? '');
return (
<div>
<p>{isMobile ? 'スマートフォン' : 'PC'} からのアクセスです</p>
<p>参照元: {referer ?? '直接アクセス'}</p>
<p>言語: {acceptLang}</p>
</div>
);
}
ヘッダー名は大文字・小文字を区別しないので、'User-Agent' でも 'user-agent' でも同じ結果になります。存在しないヘッダーを get() すると null が返るため、上の例のように ?? '' で初期値を用意しておくと安全です。
主要なメソッドの一覧
ここまでに登場した cookies() と headers() のメソッドを整理すると、次のようになります。Cookie の書き込み系メソッドは Server Action か Route Handler でのみ使える点に注意してください。
| 取得元 | メソッド | 説明 |
|---|---|---|
cookies() | get(name) | 指定した名前の Cookie を { name, value } で取得(無ければ undefined) |
cookies() | getAll() | すべての Cookie を配列で取得 |
cookies() | has(name) | Cookie が存在するかを真偽値で返す |
cookies() | set(name, value, options) | Cookie を書き込む(Server Action / Route Handler のみ) |
cookies() | delete(name) | Cookie を削除する(Server Action / Route Handler のみ) |
headers() | get(name) | 指定したリクエストヘッダーの値を取得(無ければ null・読み取り専用) |
ログイン状態で表示を出し分ける
実践的な例として、ログイントークンの Cookie があるかどうかで表示を切り替えるパターンを見てみます。token という Cookie の有無を has() で判定し、ログイン済みかどうかで表示するメッセージを変えています。サーバー側で判定するので、ログインしていない人向けの内容がクライアントに送られない、というメリットもあります。
import { cookies } from 'next/headers';
export default async function MyPage() {
const cookieStore = await cookies();
const isLoggedIn = cookieStore.has('token');
if (!isLoggedIn) {
return <p>ログインが必要です。</p>;
}
return <p>ようこそ! マイページの内容を表示します。</p>;
}
実際の認証では、Cookie の有無だけでなくトークンの検証も必要になりますが、「サーバー側で Cookie を読んで表示を分ける」という基本の形はこのとおりです。
使うとルートが動的レンダリングになる
覚えておきたい重要な性質として、cookies() や headers() を使ったコンポーネントを含むルートは、動的レンダリング(dynamic rendering)に切り替わります。これらの関数はリクエストごとに異なる情報(そのユーザーの Cookie やヘッダー)に依存するため、ビルド時に一度だけ生成する静的な HTML では対応できないからです。
つまり、そのページはアクセスのたびにサーバー側でレンダリングされます。パフォーマンスの観点では、静的に生成できるページより処理コストがかかることを意味します。もしページ全体を動的にしたくない場合は、Cookie やヘッダーに依存する部分だけを別のコンポーネントに切り出し、<Suspense> で囲むことで、影響範囲を絞るといった設計が有効です。「これらの関数を呼ぶ=そのルートは動的になる」という関係を理解しておくと、レンダリング方式で悩んだときに原因を切り分けやすくなります。
うまく動かないときに確認すること
get is not a function になる
cookieStore.get is not a function のようなエラーが出るときは、await の付け忘れがほぼ原因です。Next.js 15 以降の cookies() / headers() は Promise を返すので、await しないと Cookie オブジェクトではなく Promise に対してメソッドを呼ぶことになり、失敗します。const cookieStore = await cookies(); のように必ず解決してから使ってください。
Cookie を set したらエラーになる
通常の Server Component の中で set() や delete() を呼ぶとエラーになります。これは仕様どおりで、Cookie の変更は Server Action か Route Handler の中でしかできません。フォーム送信やボタン操作をきっかけに Cookie を書き換えたい場合は、処理を 'use server' を付けた Server Action に移すか、Route Handler(route.ts)で行いましょう。
クライアントコンポーネントで使えない
'use client' を付けたコンポーネントの中で cookies() や headers() をインポートして使おうとすると動きません。これらはサーバー側専用の関数です。クライアント側で値が必要なときは、サーバーコンポーネントで読み取った値を props として渡すか、Route Handler を経由して取得する形にしてください。
まとめ
next/headers の cookies() と headers() を使うと、Server Component・Server Action・Route Handler から、ブラウザが送ってきた Cookie やリクエストヘッダーをサーバー側で読み取れます。Next.js 15 以降はどちらも非同期になったため、const cookieStore = await cookies(); のように await を付けて使うのが基本です。Cookie は get() / getAll() / has() で読み取り、set() / delete() による書き込みは Server Action か Route Handler の中だけで可能です。headers() は読み取り専用で、User-Agent などのヘッダーを get() で取得できます。これらを使うとルートが動的レンダリングになる点、await の付け忘れや Server Component での書き込みでつまずきやすい点を押さえておけば、リクエスト情報を安全に活用できます。