next.js ssr编写组件的正确姿势?

非专业前端,既要用服务端渲染,又要用状态管理事件处理的问题。
next.js服务端组件,react相关的钩子函数,状态管理,全局状态管理,事件处理全不能用。
要用到事件处理,状态管理的话只能用客户端组件吗?那又要ssr利于seo怎么搞呢?
我想的是页面主要内容用ssr生成,需要处理事件,状态的组件只能包装一个客户端组件,在放到服务端组件里面使用,是这样吗?还是怎么处理?

阅读 269
2 个回答

先新建服务端组件:

import InteractiveWidget from '../components/InteractiveWidget';
import { getServerSideData } from '../lib/data';

export default async function ExamplePage() {

  const data = await getServerSideData();
  
  return (
    <div className="page-container">
      <h1>页面标题 (服务端渲染)</h1>
      <div className="content">
        {data.map(item => (
          <div key={item.id}>
            <h2>{item.title}</h2>
            <p>{item.description}</p>
          </div>
        ))}
      </div>
      
      {/* 嵌入客户端组件处理交互 */}
      <InteractiveWidget initialData={data[0]} />
    </div>
  );
}

再建客户端组件

"use client";

import { useState } from 'react';

export default function InteractiveWidget({ initialData }) {
  const [data, setData] = useState(initialData);
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(count + 1);
    // 处理其他状态更新
  };
  
  return (
    <div className="interactive-widget">
      <h3>交互组件 (客户端渲染)</h3>
      <p>点击次数: {count}</p>
      <p>数据: {data.title}</p>
      <button onClick={handleClick}>点击我</button>
    </div>
  );
}

服务端数据获取 + 客户端交互

//HybridCom.tsx
// 服务端父组件
import { getServerData } from '../lib/data';
import ClientPart from './ClientPart';

export default async function HybridComponent() {
  const serverData = await getServerData();
  
  return <ClientPart initialData={serverData} />;
}

ClientPart.tsx

"use client";

import { useState } from 'react';

export default function ClientPart({ initialData }) {
  const [data, setData] = useState(initialData);
  
  // 客户端交互逻辑...
  
  return (
    <div>
      {/* 交互界面 */}
    </div>
  );
}

全局状态管理方案:
providers.tsx

"use client";

import { createContext, useState } from 'react';

export const GlobalContext = createContext(null);

export function Providers({ children, initialState }) {
  const [state, setState] = useState(initialState);
  
  return (
    <GlobalContext.Provider value={{ state, setState }}>
      {children}
    </GlobalContext.Provider>
  );
}

layout.tsx


import { Providers } from './providers';
import { getInitialState } from './lib/state';

export default async function RootLayout({ children }) {
  const initialState = await getInitialState();
  
  return (
    <html>
      <body>
        <Providers initialState={initialState}>
          {children}
        </Providers>
      </body>
    </html>
  );
}

了解这些基本原理就好:

  1. 服务器端组件,可以是异步组件,可以先加载数据在渲染
  2. 客户端组件,不能是异步组件,不能在组件里加载数据,但可以在 useEffect 里加载数据
  3. 客户端组件里可以有 useState useEffect,服务器端组件不行
  4. 客户端组件会利用传入的数据进行预渲染

所以正确的做法是:

  1. 在服务器端组件完成数据初始化
  2. 将数据传给客户端组件,放心,它们都会 SSR
  3. 用户交互产生的数据变化需求,由客户端组件执行,并进行预渲染
  4. 简单来说,服务器端组件包一个客户端组件就好
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题