ヘッドレスWordPressのメニューを、Next.js 16 の Cache Components 構成でヘッダーに表示するまでの手順と、ハマったエラーのまとめ。

01環境

  • WordPress + WPGraphQL プラグイン
  • Next.js 16(cacheComponents: true)/ App Router
  • .env.local に WP_GRAPHQL_URL(例: http://localhost:8080/graphql)

02完成コード

lib/graphql.ts

import { GraphQLClient } from "graphql-request";

export const endpoint = process.env.WP_GRAPHQL_URL!;
export const client = new GraphQLClient(endpoint);

app/Header.tsx

import Link from "next/link";
import { cacheLife, cacheTag } from "next/cache";
import { endpoint } from "@/lib/graphql";

type MenuItem = { id: string; label: string; path: string };

async function getNavigationMenu(): Promise<MenuItem[]> {
  "use cache";
  cacheLife("hours");
  cacheTag("menu");

  const res = await fetch(endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      query: `
        query Menu {
          menuItems {
            nodes { id label path }
          }
        }
      `,
    }),
  });

  const json = await res.json();
  if (json.errors) {
    console.error(JSON.stringify(json.errors, null, 2));
    throw new Error("GraphQL error");
  }

  return json.data.menuItems.nodes;
}

export default async function Header() {
  const menuItems = await getNavigationMenu();

  return (
    <header>
      <nav>
        {menuItems.map((item) => (
          <Link key={item.id} href={item.path}>
            {item.label}
          </Link>
        ))}
      </nav>
    </header>
  );
}

app/layout.tsx

import { Suspense } from "react";
import Header from "./Header";

export default function RootLayout({ children }: LayoutProps<"/">) {
  return (
    <html lang="ja">
      <body>
        <Header />
        <Suspense>{children}</Suspense>
      </body>
    </html>
  );
}

03ハマったエラーと原因

エラー原因対処
Next.js encountered uncached data during prerenderingキャッシュなしの fetch / client.request を <Suspense> の外で実行していたデータ取得関数の先頭行に "use cache" を書く
Cannot read properties of undefined (reading 'menuItems')GraphQLがエラーを返し、json.data が undefined だったjson.errors をログ出力して原因を確認する
Syntax Error: Unexpected ...クエリが ...(コード例のプレースホルダ)のままだった実際のクエリに置き換える
TypeError: Invalid URLクエリで取得していない uri を new URL(item.uri) に渡していたpath(相対パス)をそのまま href に使う

04ポイント

  • graphql-request の client はURL文字列ではなくクライアントのインスタンス。fetch(client, ...) とは書けない。fetch を使うならURLを別途 export する。
  • "use cache" は関数の最初の1行に書く。cacheTag を付けておくと、WordPress更新時のWebhookから revalidateTag で再検証できる。
  • menuItems に where を付けないと全メニュー項目が返る。特定メニューだけにするには、WordPress側でメニュー位置を登録するか、menu(id: "スラッグ", idType: SLUG) で指定する。
  • メニューに外部リンクを含める場合は、path がフルURLになることがあるので、startsWith("http") で <a> と <Link> を切り替える。
  • GraphQLはクエリが不正でもHTTP 200を返す。必ず json.errors を確認する。