You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS 13如何仅在客户端渲染组件?技术求助

解决Next.js 13 SSR强制渲染导致的客户端状态异常问题

一、先排查动态加载失效的核心原因

  • 父组件没标记为客户端组件:如果动态加载的子组件被服务端渲染的父组件包裹,哪怕子组件设了ssr: false,父组件的服务端渲染逻辑还是会把子组件拉去服务端执行一遍。要么给父组件也加上'use client'指令,要么把动态加载的组件放在纯客户端层级中。
  • App Router下必须加'use client':Next.js 13的App Router是服务端优先逻辑,哪怕用dynamic设置了ssr: false,组件文件顶部也必须先加'use client',明确告知框架这是客户端组件:
    // 组件文件第一行添加
    'use client';
    
    // 后续组件逻辑...
    
    动态加载代码可以保留,但要确保组件本身已标记为客户端组件。

二、搞定Zustand持久化的Hydration报错

  • 禁止在服务端读取localStorage:Zustand的持久化中间件默认会在初始化时读取localStorage,但服务端不存在这个API,直接读取会导致服务端与客户端状态不匹配。修改存储逻辑,仅在客户端环境下读取:
    import { create } from 'zustand';
    import { persist } from 'zustand/middleware';
    
    const useStore = create(
      persist(
        (set, get) => ({
          // 服务端初始化用安全默认值,比如null
          user: null,
          // 其他状态与方法...
        }),
        {
          name: 'my-app-store',
          storage: {
            getItem: (name) => {
              // 先判断是否处于浏览器环境
              if (typeof window === 'undefined') return null;
              return localStorage.getItem(name);
            },
            setItem: (name, value) => localStorage.setItem(name, value),
            removeItem: (name) => localStorage.removeItem(name),
          },
        }
      )
    );
    
  • 延迟读取客户端状态:在使用store的组件中,用useEffect等组件挂载后再读取持久化状态,避免服务端渲染时的不匹配:
    'use client';
    import { useEffect, useState } from 'react';
    import useStore from '@/store';
    
    function UserProfile() {
      const [userData, setUserData] = useState(null);
      const { user } = useStore();
    
      useEffect(() => {
        // 组件挂载到浏览器后,再获取store的持久化数据
        setUserData(user);
      }, []);
    
      // 服务端渲染时返回加载态,避免Hydration报错
      if (!userData) return <div>加载中...</div>;
    
      return <div>你好,{userData.name}!</div>;
    }
    

三、让WebSocket只在浏览器环境运行

  • 将WebSocket初始化放入useEffect:服务端没有WebSocket API,必须把连接逻辑放在useEffect中,确保只有组件在客户端挂载后才执行:
    'use client';
    import { useEffect } from 'react';
    
    function ChatComponent() {
      useEffect(() => {
        const socket = new WebSocket('ws://你的服务器地址');
        
        socket.onmessage = (event) => {
          console.log('收到消息:', event.data); // 此时只会在浏览器控制台打印
        };
    
        // 组件卸载时关闭连接
        return () => socket.close();
      }, []);
    
      return <div>实时聊天</div>;
    }
    
  • 所有浏览器专属代码做环境判断:用到window、localStorage、WebSocket这类API的地方,都要先判断typeof window !== 'undefined',避免在服务端执行报错。

四、全局禁用SSR的终极方案

  • App Router全局转客户端渲染:如果整个项目不需要SSR,直接在根布局app/layout.js顶部加'use client',所有子组件默认成为客户端组件,不再触发服务端渲染:
    // app/layout.js
    'use client';
    
    export default function RootLayout({ children }) {
      return (
        <html lang="zh-CN">
          <body>{children}</body>
        </html>
      );
    }
    
  • Pages Router全局禁用:如果使用Pages Router,可在每个页面文件顶部加'use client'(13.4+版本支持),不建议全局配置next.config.js,避免引发其他未知问题。

内容的提问来源于stack exchange,提问作者Marcus Horne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 18:00:33