ヘッドレス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を確認する。