Next.js の title タグを Client Component から動的に変更する (App Router)
Next.js が App Router になってから、head タグ内の title タグを設定するのは専ら Server Component の責務になっている。
基本は layout.tsx や page.tsx で `export const metadata: Metadata = { title: "Something" }` として静的に定義するか、`export async function generateMetadata() {}` として動的に吐き出す方法がドキュメントで取り扱われている。
今回、"use client" ディレクティブで定義したカスタムフックのロジックを元に title タグを動的に変更したかったため、その実装を行った
(注意)
ここで記載されている内容は title タグを二重で吐き出してしまうので筋が悪い。下記で紹介している document.title = 'new title' で既存の title を上書きする形がよさそうだ
src/components/head-title/provider.tsx
"use client";
import { createContext, useState } from "react";
export const HeadTitleContext = createContext<{
title: string;
setTitle: (title: string) => void;
}>({
title: "",
setTitle: () => {},
});
export const HeadTitleProvider = ({
children,
}: { children: React.ReactNode }) => {
const [title, setTitle] = useState<string>("");
return (
<HeadTitleContext.Provider value={{ title, setTitle }}>
{children}
</HeadTitleContext.Provider>
);
};
シンプルなコンテキストを作り・・・
src/components/head-title/index.tsx
"use client";
import { useContext } from "react";
import { HeadTitleContext } from "./provider";
export { HeadTitleProvider } from "./provider";
export const useHeadTitle = () => useContext(HeadTitleContext);
export const HeadTitle = () => {
const { title } = useContext(HeadTitleContext);
if (!title) return null;
return <title>{title}</title>;
};
それを元に吐き出す HeadTitle コンポーネントとカスタムフックを作る
src/app/layout.tsx
import { HeadTitle, HeadTitleProvider } from "@/components/head-title";
export { metadata } from "./metadata";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<HeadTitleProvider>
<html lang="en">
<head>
<HeadTitle />
</head>
<body>
{children}
</body>
</html>
</HeadTitleProvider>
);
}
あとはコンテキストプロバイダーで html を囲み、 html タグ直下に head > HeadTitle コンポーネントを差し込むだけで準備ができる
なお、HeadTitle で title が存在しない場合 null を返すが、これによって従来の metadata や generateMetadata の設定を使うこともできる。コンテキストの title が存在するとそれらの設定を上書きするという仕様になる。
この仕組みを拡張すれば、title タグ以外のメタタグも自由にクライアントコンポーネントで拡張できる。
Next.js の Discussion では document.title を直接操作する方法なども紹介されている。
How to set page title and description if page is "use client" ? · vercel/next.js · Discussion #50872
使用方法
"use client";
import { useHeadTitle } from "@/components/head-title";
import { useTimer } from "@/components/timer/use-timer";
import { useEffect } from "react";
export const HeadTitleController = () => {
const { setTitle } = useHeadTitle();
const { time, status } = useTimer();
useEffect(() => {
if (status === "running") {
setTitle(`${time.mm}:${time.ss} - Web Timer`);
} else {
setTitle("");
}
}, [status, time.mm, time.ss, setTitle]);
return null;
};
元のタイトルに戻したい場合は `setTitle("")` をするだけで良い
この方法ではコンポーネントを用意しているが、もちろんカスタムフック内での仕様も可能だ