Next.js 13动态页面标题实现异常求助
Next.js 13 动态页面标题跳转后不更新的解决方法
问题背景
在Next.js 13中实现动态页面标题时遇到异常:按照官方文档提示使用客户端组件结合useEffect更新document.title后,首次跳转标题可能正常生效,但通过next/link来回跳转后标题不再变化。
官方文档提示
警告:目前,使用
next/link进行客户端跳转时,<Head>导出不会重新渲染,仅在初始渲染和页面刷新时生效。要解决<title>的这个问题,你可以使用带有useEffect的客户端组件来更新document.title。我们计划在未来版本中修复此问题。
用户尝试的代码
'use client'; import { useEffect } from "react"; import Head from 'next/head'; interface Props { params: {slug : string}, searchParams: {id:string} }; const getPost = async (id: string) => { const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`); const posts = await res.json(); return posts; }; const Page = ({params, searchParams}: Props) => { useEffect(() => { const setDocumentTitle = async () => { const post = await getPost(params.slug); document.title = post.title; } setDocumentTitle(); }, [params.slug]); return (<> <Head> <title>Blog Post</title> </Head> <div> <h1>Blog Post</h1> <p>Slug: {params.slug}</p> <p>Id: {searchParams.id}</p> </div> </>); }; export default Page;
问题根源
<Head>组件中的静态<title>标签会在客户端跳转时优先生效,覆盖useEffect异步设置的动态标题;同时组件复用机制可能导致useEffect未正确触发更新。
解决方法
方案1:移除静态<Head>标题(快速修复)
直接删除<Head>中的静态<title>标签,仅保留useEffect动态设置逻辑:
'use client'; import { useEffect } from "react"; interface Props { params: {slug : string}, searchParams: {id:string} }; const getPost = async (id: string) => { const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`); return await res.json(); }; const Page = ({params, searchParams}: Props) => { useEffect(() => { const setDocumentTitle = async () => { const post = await getPost(params.slug); document.title = post.title; } setDocumentTitle(); }, [params.slug]); return ( <div> <h1>Blog Post</h1> <p>Slug: {params.slug}</p> <p>Id: {searchParams.id}</p> </div> ); }; export default Page;
方案2:使用官方推荐的generateMetadata(最佳实践)
在App Router中,官方推荐使用generateMetadata服务器端函数生成动态元数据,无需客户端组件即可实现全场景标题更新:
import type { Metadata } from 'next'; interface Props { params: {slug : string}, searchParams: {id:string} }; const getPost = async (id: string) => { const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, { cache: 'no-store' }); return await res.json(); }; export async function generateMetadata({ params }: Props): Promise<Metadata> { const post = await getPost(params.slug); return { title: post.title, }; } const Page = ({params, searchParams}: Props) => { return ( <div> <h1>Blog Post</h1> <p>Slug: {params.slug}</p> <p>Id: {searchParams.id}</p> </div> ); }; export default Page;
注意:
generateMetadata是服务器组件函数,无需添加'use client'指令;若需实时数据,需设置cache: 'no-store'或配置revalidate参数。
方案3:添加清理函数确保更新可靠性
若必须保留<Head>组件,可通过标记组件挂载状态避免异步请求竞态,并将<Head>标题改为动态值:
'use client'; import { useEffect } from "react"; import Head from 'next/head'; interface Props { params: {slug : string}, searchParams: {id:string} }; const getPost = async (id: string) => { const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`); return await res.json(); }; const Page = ({params, searchParams}: Props) => { useEffect(() => { let isMounted = true; const setDocumentTitle = async () => { const post = await getPost(params.slug); if (isMounted) { document.title = post.title; } } setDocumentTitle(); return () => { isMounted = false; }; }, [params.slug]); return (<> <Head> <title>{params.slug}</title> </Head> <div> <h1>Blog Post</h1> <p>Slug: {params.slug}</p> <p>Id: {searchParams.id}</p> </div> </>); }; export default Page;
内容的提问来源于stack exchange,提问作者ggrigh
相关产品推荐
相关产品推荐

