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

Next.js 13(App Router)服务端与客户端组件模式选型及实践困惑

Next.js 13 App Router 功能选择与实战方案

一、核心原则:先明确渲染场景再选功能

Next.js 13的App Router没有绝对的“最佳实践”,但所有选择都围绕服务端渲染(SSR)/静态生成(SSG)的性能优势和客户端交互的灵活性平衡,优先让服务端做它擅长的事:数据获取、静态内容渲染;客户端只处理交互逻辑。


二、各功能场景的选择逻辑

1. 服务端/客户端组件模式

  • 叶子节点放客户端组件:纯交互组件(比如输入框、按钮)优先这么放,避免服务端组件被客户端特性污染。如果需要服务端感知客户端状态,优先用URL参数(服务端能直接读取),全局状态(比如Redux)只在跨页面共享复杂状态时用,别滥用。
  • 客户端嵌套服务端组件:可以传props,但只能传JSON兼容的序列化数据(不能传函数、DOM对象)。服务端组件确实拿不到客户端状态,但如果客户端组件先把状态转成可序列化的props(比如把URL参数转成字符串),再传给嵌套的服务端组件是完全可行的。
  • 服务端组件用查询参数:你搞错了一点——不只是page.tsx,layout.tsx也能接收searchParams作为props!直接在layout里拿到查询参数,再往下传就不用大量透传了。

2. 数据获取

  • 服务端纯TS/JS加载:首屏数据、静态数据、非用户专属数据优先用这个,减少客户端请求,提升加载速度。
  • 服务端加载后传props给客户端:适合客户端需要交互(比如点击筛选)但初始数据来自服务端的场景,注意props必须是JSON兼容的。
  • 客户端用Server Actions获取数据:需要服务端权限验证(比如用户专属数据)、操作敏感数据时用,自动处理CSRF,比fetch更安全简洁。
  • 客户端用fetch:公开数据、不需要权限的场景用,灵活性高,但要自己处理错误和CSRF(Next.js会自动注入令牌,请求头带X-CSRF-Token就行)。
  • Redux等状态管理:只适合跨组件共享复杂状态(比如购物车),如果只是单纯获取数据,优先用Next.js自带的服务端能力,别过度复杂化。

3. 数据提交

  • Server Actions:优先选,自动处理CSRF、表单pending状态,代码更简洁,覆盖90%以上的表单提交场景。
  • fetch:需要自定义请求头、处理复杂响应时用,但要手动处理CSRF令牌。

三、你的搜索布局问题:正确实现方式

你遇到的核心问题是误以为layout拿不到searchParams,其实App Router的layout组件可以直接接收这个props,具体实现如下:

1. 修改Layout组件,接收searchParams

export default function Layout({ children, searchParams }: { 
  children: React.ReactNode; 
  searchParams: { [key: string]: string | string[] | undefined } 
}) {
  return (
    <div className="flex flex-col">
      <div className="prose">
        <h1>Algorithm</h1>
        {/* 把查询参数传给AlgorithmSearch */}
        <AlgorithmSearch searchQuery={searchParams.q as string} />
        <div>{children}</div>
      </div>
    </div>
  );
}

2. 实现AlgorithmSearch组件

拆分客户端交互和服务端数据渲染:

// AlgorithmSearch.tsx(服务端组件,因为里面的OptionsDisplay是服务端组件)
import SearchField from './SearchField';
import OptionsDisplay from './OptionsDisplay';

export default function AlgorithmSearch({ searchQuery }: { searchQuery?: string }) {
  return (
    <div>
      {/* 客户端组件负责输入和修改URL */}
      <SearchField initialQuery={searchQuery} />
      {/* 服务端组件根据查询参数渲染选项 */}
      {searchQuery && <OptionsDisplay query={searchQuery} />}
    </div>
  );
}

// SearchField.tsx(客户端组件,加'use client')
'use client';
import { useRouter } from 'next/navigation';
import { useState } from 'react';

export default function SearchField({ initialQuery }: { initialQuery?: string }) {
  const [query, setQuery] = useState(initialQuery || '');
  const router = useRouter();

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setQuery(e.target.value);
    // 修改URL查询参数,触发服务端重新渲染
    router.push(`?q=${encodeURIComponent(e.target.value)}`, { scroll: false });
  };

  return (
    <input 
      type="text" 
      value={query} 
      onChange={handleChange} 
      placeholder="Search algorithms..." 
    />
  );
}

// OptionsDisplay.tsx(服务端组件,直接在服务端查数据)
export default async function OptionsDisplay({ query }: { query: string }) {
  // 这里写服务端数据获取逻辑,比如用Prisma查数据库
  const algorithms = await fetchAlgorithmsFromDB(query);
  
  return (
    <ul className="mt-2">
      {algorithms.map(alg => (
        <li key={alg.id} className="py-1 hover:bg-gray-100">
          {alg.name}
        </li>
      ))}
    </ul>
  );
}

3. 选中选项后跳转逻辑

在OptionsDisplay的列表项里用Next.js的Link组件跳转,此时children区域的page组件会根据路由参数加载对应内容,这部分也是服务端渲染的:

// OptionsDisplay.tsx里的列表项修改
import Link from 'next/link';

<li key={alg.id} className="py-1 hover:bg-gray-100 cursor-pointer">
  <Link href={`/algorithm/${alg.id}`}>{alg.name}</Link>
</li>

内容的提问来源于stack exchange,提问作者Alfred Madere

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 04:12:04