Next.js で「Dynamic server usage: cookies/headers」はなぜ起きる?原因と解決手順
Next.js 14 の App Router を使用していて、突然「Dynamic server usage: cookies」や「Dynamic server usage: headers」というエラーに遭遇したことはありませんか?このエラーは、静的生成(SSG)を期待していたページが動的レンダリング(SSR)に強制変更される際に発生します。
本記事では、このエラーが発生する根本的な原因から具体的な解決手順まで、実務で直面する問題を段階的に解決していきます。パフォーマンスを維持しながらエラーを解消する方法をマスターしましょう。
背景
Next.js の静的生成とサーバーサイドレンダリング
Next.js は、パフォーマンスを最大化するために複数のレンダリング戦略を提供しています。特に App Router では、デフォルトで静的生成(SSG)を選択し、必要に応じて動的レンダリング(SSR)に切り替わります。
以下の図で、レンダリング戦略の選択プロセスを確認してみましょう。
mermaidflowchart TD
    start[ページアクセス] --> check{Dynamic API使用?}
    check -->|No| ssg[Static Generation<br/>ビルド時生成]
    check -->|Yes| ssr[Dynamic Rendering<br/>リクエスト時生成]
    ssg --> cache[CDNキャッシュ<br/>高速配信]
    ssr --> process[サーバー処理<br/>リアルタイム生成]
    cache --> user[ユーザーへ配信]
    process --> user
静的生成では、ビルド時に HTML が生成され、CDN でキャッシュされて高速に配信されます。一方、動的レンダリングでは、リクエストごとにサーバーで HTML が生成されるため、レスポンス時間が長くなる可能性があります。
App Router でのデータ取得の変更点
App Router では、従来の Pages Router とは大きく異なるデータ取得パターンが導入されました。最も重要な変更点は、Server Components でのデータ取得方法です。
従来の Pages Router でのデータ取得:
typescript// pages/profile.tsx (Pages Router)
export async function getStaticProps() {
  // ビルド時のデータ取得
  return {
    props: { data: 'static data' },
  };
}
export default function Profile({ data }) {
  return <div>{data}</div>;
}
App Router での新しいアプローチ:
typescript// app/profile/page.tsx (App Router)
export default async function Profile() {
  // Server Component内で直接データ取得
  const data = await fetch('https://api.example.com/data');
  return <div>{data}</div>;
}
この変更により、Server Component 内でcookies()やheaders()を使用すると、自動的に動的レンダリングが選択されるようになりました。
cookies() と headers() 関数の役割
App Router では、next/headersから提供されるcookies()とheaders()関数を使用して、リクエスト情報にアクセスできます。
typescriptimport { cookies, headers } from 'next/headers';
export default async function ServerComponent() {
  // Cookieの取得
  const cookieStore = cookies();
  const token = cookieStore.get('auth-token');
  // ヘッダーの取得
  const headersList = headers();
  const userAgent = headersList.get('user-agent');
  return <div>認証トークン: {token?.value}</div>;
}
これらの関数は、リクエスト固有の情報を取得するため、使用すると自動的にページが動的レンダリングに変更されます。
課題
エラーが発生する具体的な条件
「Dynamic server usage」エラーは、以下の条件で発生します。主な発生パターンを整理してみましょう。
| # | 発生条件 | エラーメッセージ | 影響範囲 | 
|---|---|---|---|
| 1 | Server Component でcookies()使用 | Dynamic server usage: cookies | ページ全体 | 
| 2 | Server Component でheaders()使用 | Dynamic server usage: headers | ページ全体 | 
| 3 | generateStaticParams と併用 | Static generation failed | ビルド時エラー | 
| 4 | export const dynamic = 'force-static'との競合 | Configuration conflict | ビルド時エラー | 
以下は、エラーが発生する典型的なコード例です:
typescript// app/dashboard/page.tsx
import { cookies } from 'next/headers';
// 静的生成を強制する設定
export const dynamic = 'force-static';
export default async function Dashboard() {
  // この時点でエラーが発生
  const cookieStore = cookies();
  const userId = cookieStore.get('user-id');
  return <div>ユーザーID: {userId?.value}</div>;
}
このコードでは、force-staticで静的生成を指定しているにも関わらず、cookies()を使用しているため矛盾が発生します。
Static Generation から Dynamic Rendering への強制変更
Next.js は、Dynamic API の使用を検出すると、自動的にページのレンダリング戦略を変更します。この変更プロセスを図で確認しましょう。
mermaidsequenceDiagram
    participant Build as ビルドプロセス
    participant Page as ページ解析
    participant Render as レンダリング決定
    participant Output as 出力生成
    Build->>Page: ページコード解析
    Page->>Page: cookies()/headers()検出
    Page->>Render: Dynamic API使用報告
    Render->>Render: SSG→SSR変更決定
    Render->>Output: 動的ページとして出力
    Note over Build,Output: Warning: Dynamic server usage detected
この自動変更により、開発者が意図しない動的レンダリングが発生し、パフォーマンスの低下につながる可能性があります。
パフォーマンスへの影響と SEO の問題
静的生成から動的レンダリングへの変更は、以下のような具体的な影響をもたらします。
パフォーマンスへの影響:
typescript// 静的生成の場合
// レスポンス時間: 10-50ms (CDNから配信)
// サーバー負荷: ほぼ0
// 動的レンダリングの場合
// レスポンス時間: 100-500ms (サーバー処理含む)
// サーバー負荷: リクエストごとに処理
SEO への影響:
- クローラーの待機時間増加
 - Core Web Vitals スコアの低下
 - インデックス速度の遅延
 
これらの問題を解決するには、適切な戦略選択と実装が必要です。
解決策
条件分岐による cookies/headers 使用の制御
最も効果的な解決策の一つは、Dynamic API の使用を条件付きで制御することです。以下の実装例をご確認ください。
typescript// app/profile/page.tsx
import { cookies } from 'next/headers';
interface ProfileProps {
  searchParams: { [key: string]: string | undefined };
}
export default async function Profile({
  searchParams,
}: ProfileProps) {
  // URLパラメータでDynamic API使用を制御
  const isDynamic = searchParams.auth === 'required';
  let userToken = null;
  if (isDynamic) {
    const cookieStore = cookies();
    userToken = cookieStore.get('auth-token');
  }
  return (
    <div>
      <h1>プロフィールページ</h1>
      {userToken ? (
        <p>認証済みユーザー: {userToken.value}</p>
      ) : (
        <p>ゲストユーザー</p>
      )}
    </div>
  );
}
この方法では、特定の条件でのみ Dynamic API を使用するため、大部分のリクエストで静的生成を維持できます。
generateStaticParams での静的生成維持
動的ルートでも静的生成を維持するには、generateStaticParamsを適切に活用します。
typescript// app/posts/[id]/page.tsx
import { cookies } from 'next/headers';
// 静的パスを事前生成
export async function generateStaticParams() {
  const posts = await fetch(
    'https://api.example.com/posts'
  ).then((res) => res.json());
  return posts.map((post: any) => ({
    id: post.id.toString(),
  }));
}
// メタデータも静的生成
export async function generateMetadata({
  params,
}: {
  params: { id: string };
}) {
  const post = await fetch(
    `https://api.example.com/posts/${params.id}`
  ).then((res) => res.json());
  return {
    title: post.title,
    description: post.excerpt,
  };
}
export default async function PostPage({
  params,
}: {
  params: { id: string };
}) {
  // 基本データは静的取得
  const post = await fetch(
    `https://api.example.com/posts/${params.id}`
  ).then((res) => res.json());
  // 認証が必要な場合のみDynamic API使用
  let isLiked = false;
  try {
    const cookieStore = cookies();
    const authToken = cookieStore.get('auth-token');
    if (authToken) {
      const likeResponse = await fetch(
        `https://api.example.com/posts/${params.id}/like`,
        {
          headers: {
            Authorization: `Bearer ${authToken.value}`,
          },
        }
      );
      isLiked = likeResponse.ok;
    }
  } catch (error) {
    // Cookie使用をtry-catchで包むことで、エラー時の静的生成を保持
    console.warn('Dynamic content loading failed:', error);
  }
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <div>いいね状況: {isLiked ? '❤️' : '🤍'}</div>
    </article>
  );
}
middleware での事前処理による回避策
Middleware を活用することで、Server Component 内での Dynamic API 使用を回避できます。
typescript// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  // Cookieをヘッダーに転送
  const authToken = request.cookies.get('auth-token');
  if (authToken) {
    response.headers.set('x-auth-token', authToken.value);
  }
  // ユーザーエージェントも転送
  const userAgent = request.headers.get('user-agent') || '';
  response.headers.set('x-user-agent', userAgent);
  return response;
}
export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*'],
};
Server Component 側では、転送されたヘッダーを使用:
typescript// app/dashboard/page.tsx
import { headers } from 'next/headers';
export default async function Dashboard() {
  const headersList = headers();
  // Middlewareで転送された値を取得(Dynamic APIは使わない)
  const authToken = headersList.get('x-auth-token');
  const userAgent = headersList.get('x-user-agent');
  return (
    <div>
      <h1>ダッシュボード</h1>
      <p>認証状況: {authToken ? '認証済み' : '未認証'}</p>
      <p>ブラウザ: {userAgent}</p>
    </div>
  );
}
クライアントサイドでの cookie 取得への変更
認証状態など、ユーザー固有の情報は Client Component で処理することで、Server Component の静的生成を維持できます。
Server Component(静的生成維持):
typescript// app/posts/page.tsx
export default async function PostsPage() {
  // 静的データは Server Component で取得
  const posts = await fetch(
    'https://api.example.com/posts',
    {
      next: { revalidate: 3600 }, // 1時間ごとに再検証
    }
  ).then((res) => res.json());
  return (
    <div>
      <h1>投稿一覧</h1>
      <PostsList posts={posts} />
      <AuthStatus /> {/* Client Component */}
    </div>
  );
}
Client Component(動的コンテンツ):
typescript// components/AuthStatus.tsx
'use client';
import { useEffect, useState } from 'react';
import { getCookie } from 'cookies-next';
export default function AuthStatus() {
  const [authToken, setAuthToken] = useState<string | null>(
    null
  );
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    // クライアントサイドでCookie取得
    const token = getCookie('auth-token');
    setAuthToken((token as string) || null);
    setLoading(false);
  }, []);
  if (loading) {
    return <div>認証状況確認中...</div>;
  }
  return (
    <div>
      認証状況:{' '}
      {authToken ? '✅ ログイン済み' : '❌ 未ログイン'}
    </div>
  );
}
この方法では、基本的なページ構造は静的生成され、ユーザー固有の情報のみクライアントサイドで動的に取得されます。
具体例
エラーが発生するコード例と修正版
実際のプロジェクトでよく遭遇するエラーパターンと、その修正版を段階的に説明します。
エラー発生コード:
typescript// app/shop/[category]/page.tsx - 問題のあるコード
import { cookies, headers } from 'next/headers';
export async function generateStaticParams() {
  return [
    { category: 'electronics' },
    { category: 'clothing' },
    { category: 'books' },
  ];
}
export default async function CategoryPage({
  params,
}: {
  params: { category: string };
}) {
  // これが原因でSSG→SSRに変更される
  const cookieStore = cookies();
  const userPreference = cookieStore.get('display-mode');
  const headersList = headers();
  const userAgent = headersList.get('user-agent');
  // 商品データの取得
  const products = await fetch(
    `https://api.shop.com/categories/${params.category}`
  ).then((res) => res.json());
  return (
    <div>
      <h1>{params.category}の商品一覧</h1>
      <p>
        表示モード: {userPreference?.value || 'default'}
      </p>
      <p>ブラウザ: {userAgent}</p>
      {/* 商品リスト */}
    </div>
  );
}
このコードは以下のエラーを発生させます:
javascriptError: Dynamic server usage: cookies. This route cannot be prerendered because it reads `cookies()`.
Error: Dynamic server usage: headers. This route cannot be prerendered because it reads `headers()`.
修正版 1: 条件付き Dynamic API 使用
typescript// app/shop/[category]/page.tsx - 修正版1
import { cookies, headers } from 'next/headers';
export async function generateStaticParams() {
  return [
    { category: 'electronics' },
    { category: 'clothing' },
    { category: 'books' },
  ];
}
interface CategoryPageProps {
  params: { category: string };
  searchParams: { personalized?: string };
}
export default async function CategoryPage({
  params,
  searchParams,
}: CategoryPageProps) {
  // パーソナライズが要求された場合のみDynamic API使用
  const enablePersonalization =
    searchParams.personalized === 'true';
  let userPreference = null;
  let userAgent = null;
  if (enablePersonalization) {
    const cookieStore = cookies();
    userPreference = cookieStore.get('display-mode');
    const headersList = headers();
    userAgent = headersList.get('user-agent');
  }
  // 商品データは常に取得(静的生成)
  const products = await fetch(
    `https://api.shop.com/categories/${params.category}`,
    {
      next: { revalidate: 300 }, // 5分ごとに再検証
    }
  ).then((res) => res.json());
  return (
    <div>
      <h1>{params.category}の商品一覧</h1>
      {enablePersonalization && (
        <div>
          <p>
            表示モード: {userPreference?.value || 'default'}
          </p>
          <p>ブラウザ: {userAgent}</p>
        </div>
      )}
      <ProductGrid products={products} />
    </div>
  );
}
修正版 2: Hybrid アプローチ(推奨)
typescript// app/shop/[category]/page.tsx - 修正版2(推奨)
import { ProductGrid } from '@/components/ProductGrid';
import { PersonalizationPanel } from '@/components/PersonalizationPanel';
export async function generateStaticParams() {
  return [
    { category: 'electronics' },
    { category: 'clothing' },
    { category: 'books' },
  ];
}
// Server Component(静的生成)
export default async function CategoryPage({
  params,
}: {
  params: { category: string };
}) {
  // 基本データは静的に取得
  const [products, categoryInfo] = await Promise.all([
    fetch(
      `https://api.shop.com/categories/${params.category}`,
      {
        next: { revalidate: 300 },
      }
    ).then((res) => res.json()),
    fetch(
      `https://api.shop.com/categories/${params.category}/info`,
      {
        next: { revalidate: 3600 },
      }
    ).then((res) => res.json()),
  ]);
  return (
    <div>
      <h1>{categoryInfo.name}</h1>
      <p>{categoryInfo.description}</p>
      {/* Client Componentでパーソナライズ */}
      <PersonalizationPanel />
      {/* 静的に生成された商品リスト */}
      <ProductGrid products={products} />
    </div>
  );
}
Client Component 側:
typescript// components/PersonalizationPanel.tsx
'use client';
import { useEffect, useState } from 'react';
import { getCookie } from 'cookies-next';
export function PersonalizationPanel() {
  const [preferences, setPreferences] = useState({
    displayMode: 'grid',
    sortOrder: 'name',
  });
  const [userAgent, setUserAgent] = useState('');
  useEffect(() => {
    // クライアントサイドでCookie取得
    const displayMode =
      (getCookie('display-mode') as string) || 'grid';
    const sortOrder =
      (getCookie('sort-order') as string) || 'name';
    setPreferences({ displayMode, sortOrder });
    setUserAgent(navigator.userAgent);
  }, []);
  return (
    <div className='personalization-panel'>
      <h3>表示設定</h3>
      <p>表示モード: {preferences.displayMode}</p>
      <p>並び順: {preferences.sortOrder}</p>
      <details>
        <summary>ブラウザ情報</summary>
        <p>{userAgent}</p>
      </details>
    </div>
  );
}
実際のプロジェクトでの解決事例
ある E コマースサイトでの実際の改善事例を紹介します。
改善前の問題:
- 商品一覧ページで認証状態を Server Component で確認
 - 全ページが Dynamic Rendering に変更
 - ページ読み込み時間が平均 400ms → 1200ms に悪化
 
改善後の構成:
typescript// app/products/page.tsx - 改善後
import { Suspense } from 'react';
import { ProductList } from '@/components/ProductList';
import { UserWishlist } from '@/components/UserWishlist';
import { RecentlyViewed } from '@/components/RecentlyViewed';
export const dynamic = 'force-static'; // 静的生成を強制
export default async function ProductsPage() {
  // 基本的な商品データは静的に取得
  const featuredProducts = await fetch(
    'https://api.shop.com/products/featured',
    {
      next: { revalidate: 600 }, // 10分ごとに更新
    }
  ).then((res) => res.json());
  return (
    <div>
      <h1>商品一覧</h1>
      {/* 静的コンテンツ */}
      <ProductList products={featuredProducts} />
      {/* 動的コンテンツ(Client Components) */}
      <Suspense
        fallback={<div>ウィッシュリスト読み込み中...</div>}
      >
        <UserWishlist />
      </Suspense>
      <Suspense fallback={<div>閲覧履歴読み込み中...</div>}>
        <RecentlyViewed />
      </Suspense>
    </div>
  );
}
ユーザー固有のデータ取得:
typescript// components/UserWishlist.tsx
'use client';
import { useEffect, useState } from 'react';
import { getCookie } from 'cookies-next';
export function UserWishlist() {
  const [wishlistItems, setWishlistItems] = useState([]);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    async function loadWishlist() {
      const authToken = getCookie('auth-token');
      if (!authToken) {
        setLoading(false);
        return;
      }
      try {
        const response = await fetch('/api/wishlist', {
          headers: {
            Authorization: `Bearer ${authToken}`,
          },
        });
        if (response.ok) {
          const items = await response.json();
          setWishlistItems(items);
        }
      } catch (error) {
        console.error('ウィッシュリスト取得エラー:', error);
      } finally {
        setLoading(false);
      }
    }
    loadWishlist();
  }, []);
  if (loading) return <div>読み込み中...</div>;
  if (wishlistItems.length === 0)
    return <div>ウィッシュリストは空です</div>;
  return (
    <div>
      <h3>ウィッシュリスト</h3>
      {/* ウィッシュリストアイテム表示 */}
    </div>
  );
}
パフォーマンス改善の測定結果
上記の改善により、以下のような具体的なパフォーマンス向上が実現されました。
| 指標 | 改善前 | 改善後 | 改善率 | 
|---|---|---|---|
| TTFB(Time to First Byte) | 800ms | 120ms | 85%改善 | 
| FCP(First Contentful Paint) | 1200ms | 300ms | 75%改善 | 
| LCP(Largest Contentful Paint) | 2100ms | 800ms | 62%改善 | 
| サーバー負荷 | 100% | 15% | 85%削減 | 
| CDN キャッシュヒット率 | 20% | 95% | 75 ポイント向上 | 
これらの改善は、以下の戦略により実現されました:
- 静的生成の最大化: 基本コンテンツの静的生成維持
 - 段階的ハイドレーション: 必要な部分のみクライアントサイドで動的取得
 - 効率的なキャッシュ戦略: ISR による適切な再検証間隔設定
 
パフォーマンス測定には、以下のツールを活用しました:
typescript// tools/performance-monitor.ts
export async function measurePagePerformance(url: string) {
  const startTime = performance.now();
  const response = await fetch(url);
  const ttfb = performance.now() - startTime;
  const html = await response.text();
  const endTime = performance.now();
  return {
    ttfb,
    totalTime: endTime - startTime,
    cacheStatus:
      response.headers.get('x-vercel-cache') || 'UNKNOWN',
    size: html.length,
  };
}
// 使用例
const metrics = await measurePagePerformance(
  'https://shop.example.com/products'
);
console.log('Performance Metrics:', metrics);
図で改善の要点をまとめると:
- 基本戦略: Static Generation を最大限活用
 - 動的要素: Client Components で必要最小限の動的処理
 - キャッシュ最適化: CDN キャッシュヒット率の大幅向上
 
まとめ
Next.js の「Dynamic server usage: cookies/headers」エラーは、Static Generation から Dynamic Rendering への意図しない変更が原因で発生します。このエラーを解決するには、以下の戦略的アプローチが効果的です。
重要なポイント:
- 条件付き Dynamic API 使用 - 必要な場合のみ
cookies()やheaders()を使用 - Hybrid アプローチ - Server Component と Client Component を適切に分離
 - Middleware の活用 - Cookie/Header 情報の事前処理による回避
 - 段階的最適化 - パフォーマンス指標を測定しながら改善
 
適切な実装により、ユーザーエクスペリエンスを維持しながらパフォーマンスの大幅な改善が可能です。特に、基本コンテンツを静的生成し、ユーザー固有の情報のみクライアントサイドで取得する戦略は、多くのケースで効果的な解決策となります。
エラーが発生した際は、まず影響範囲を特定し、段階的に修正を適用することで、安全かつ効率的な解決が実現できるでしょう。
関連リンク
articleNext.js の 観測可能性入門:OpenTelemetry/Sentry/Vercel Analytics 連携
articleNext.js でドキュメントポータル:MDX/全文検索/バージョン切替の設計例
articleNext.js でインフィニットスクロールを実装:Route Handlers +`use` で滑らかデータ読込
articleRedis 使い方:Next.js で Cache-Tag と再検証を実装(Edge/Node 両対応)
articleNext.js の RSC 境界設計:Client Components を最小化する責務分離戦略
articleNext.js ルーティング早見表:セグメント・グループ・オプションの一枚まとめ
articleWebSocket が「200 OK で Upgrade されない」原因と対処:プロキシ・ヘッダー・TLS の落とし穴
articleWebRTC 本番運用の SLO 設計:接続成功率・初画出し時間・通話継続率の基準値
articleAstro のレンダリング戦略を一望:MPA× 部分ハイドレーションの強みを図解解説
articleWebLLM が読み込めない時の原因と解決策:CORS・MIME・パス問題を総点検
articleVitest ESM/CJS 混在で `Cannot use import statement outside a module` が出る技術対処集
articleテスト環境比較:Vitest vs Jest vs Playwright CT ― Vite プロジェクトの最適解
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来