1. ホーム
  2. Next.js

【Next.js】cookies・headers の使い方|サーバーコンポーネントでリクエスト情報を読み取る

Share

Next.js の App Router では、サーバー側で動くコンポーネントから、ブラウザが送ってきた Cookie やリクエストヘッダーを直接読み取れます。そのために用意されているのが next/headerscookies()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 にしておきます。

app/page.tsx
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つです。

app/page.tsx
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 を書き換えます。

app/actions.ts
'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() でヘッダーの値を取得できます。こちらは読み取り専用で、リクエストヘッダーを書き換えることはできません。

app/page.tsx
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() で判定し、ログイン済みかどうかで表示するメッセージを変えています。サーバー側で判定するので、ログインしていない人向けの内容がクライアントに送られない、というメリットもあります。

app/mypage/page.tsx
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/headerscookies()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 での書き込みでつまずきやすい点を押さえておけば、リクエスト情報を安全に活用できます。

参考ページ