You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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 不匹配——服务器渲染时不会执行客户端组件里的异步代码,而客户端渲染时会执行,两者内容不一致就会导致偶发错误(有时候缓存或者路由切换时才会暴露出来)。

正确的做法是拆分组件,分离服务器和客户端逻辑:

  1. 把根布局改回服务器组件:去掉根布局的'use client',在服务器端异步获取Prismic的导航数据,这样数据会在服务器渲染时就准备好,避免客户端 hydration 不匹配。
  2. 把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

火山引擎 最新活动