Prismic CMS与Next.js 14 App Router中'use client'相关偶发错误排查
Prismic CMS与Next.js 14 App Router中'use client'相关偶发错误排查
嘿,我看了你的问题,这种偶发的错误确实让人头疼,结合你的代码和Next.js 14 App Router的规则,我帮你梳理几个关键问题和解决方案:
首先,先揪出最明显的一个bug——你在导航链接的className判断里用了赋值运算符=,而不是比较运算符===:
// 错误写法:这会直接修改router.pathname的值! className={`no-underline ${(router.pathname = item.link ? active : inactive)}`}
这个错误会意外修改路由状态,绝对是偶发错误的元凶之一,先把它改成比较逻辑,而且要注意Prismic的链接字段是有url属性的,正确写法应该是:
className={`no-underline ${pathname === item.link?.url ? active : inactive}`}
接下来,说说你怀疑的useRouter/usePathname和'use client'的问题:
你把整个根布局都标记成了'use client',还在里面写了一个异步的NavBar函数,这在Next.js App Router里很容易触发 hydration 不匹配——服务器渲染时不会执行客户端组件里的异步代码,而客户端渲染时会执行,两者内容不一致就会导致偶发错误(有时候缓存或者路由切换时才会暴露出来)。
正确的做法是拆分组件,分离服务器和客户端逻辑:
- 把根布局改回服务器组件:去掉根布局的
'use client',在服务器端异步获取Prismic的导航数据,这样数据会在服务器渲染时就准备好,避免客户端 hydration 不匹配。 - 把NavBar做成独立的客户端组件:只在NavBar里加
'use client'(因为它需要用到usePathname这个客户端钩子),然后把服务器端获取的导航数据通过props传给它。
修改后的layout.js:
import { PrismicPreview, PrismicNextLink, PrismicNextImage } from '@prismicio/next'; import { repositoryName, createClient } from '@/prismicio'; import './globals.css'; import NavBar from './components/NavBar'; export default async function RootLayout({ children }) { // 服务器端安全获取导航数据 const client = createClient(); const nav = await client.getSingle('navigation_menu'); return ( <html lang='en'> <body> <NavBar nav={nav} /> {children} <PrismicPreview repositoryName={repositoryName} /> {/* <Footer /> */} </body> </html> ); }
修改后的components/NavBar.js:
'use client'; import { usePathname } from 'next/navigation'; import { PrismicNextLink, PrismicNextImage } from '@prismicio/next'; export default function NavBar({ nav }) { const pathname = usePathname(); const inactive = 'hover:border-purpleDefault hover:border-b-2'; const active = 'border-lavender border-b-2'; return ( <div className='flex justify-center items-center font-semibold max-w-full mx-auto py-2'> <div className='container flex justify-between'> <span className='text-2xl leading-6 font-logo flex flex-row items-center'> {nav.data.company_name} <PrismicNextImage field={nav.data.company_logo} className='h-20 w-20 ml-4' /> </span> <ul className='flex items-center text-2xl'> {nav.data.menu_items.map((item) => { // 用Prismic链接的url做判断,同时处理可能的空值 const linkUrl = item.link?.url || ''; // 尽量用item的id做key,比JSON.stringify更稳定 return ( <li key={item.id || JSON.stringify(item)}> <PrismicNextLink field={item.link} className={`no-underline ${pathname === linkUrl ? active : inactive}`} > {item.label} </PrismicNextLink> </li> ); })} </ul> </div> </div> ); }
为什么这样改能解决偶发错误?
- 服务器端获取数据:导航数据在页面渲染前就已经准备好,服务器和客户端渲染的内容完全一致,不会出现hydration不匹配的问题。
- 缩小客户端组件范围:只有NavBar是客户端组件,根布局和其他组件依然保持服务器渲染的性能优势,同时避免了全局
'use client'带来的状态异常。 - 修复了路由赋值错误:不会再意外修改路由状态,消除了一个重要的偶发错误诱因。
最后再提醒你几个小细节:
- 不要随便给根布局加
'use client',这会让整个应用失去服务器渲染的性能优势。 - 列表项的key尽量用Prismic提供的唯一id(
item.id),比JSON.stringify(item)更可靠,避免不必要的组件重渲染。
备注:内容来源于stack exchange,提问作者techmeowt




