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

Next.js App Router服务端组件如何用onClick实现加载更多文章

Next.js服务端组件实现onClick加载更多的最简方案

不需要把整个PostList改成客户端组件——服务端组件的异步数据预取优势可以保留,只需要把交互逻辑部分抽成独立的客户端组件即可,这是最符合Next.js App Router设计理念的方案。

具体实现步骤

1. 改造原服务端组件PostList

让它接收numOfPosts作为props,不再硬写固定数值:

export async function getData(category: string, numOfPosts: number) {
  const res = await getPostsByCategory(category, numOfPosts);

  if (!res) {
    throw new Error("Failed to fetch data");
  }

  return res;
}

// 依然是服务端组件,负责数据预取和列表渲染
const PostList = async ({ category, numOfPosts }: { category: string; numOfPosts: number }) => {
  const data = await getData(category, numOfPosts);

  return (
    <div>
      {data.map((item: any, idx: number) => {
        return (
          <div
            key={idx}
            className={`${styles.grid2} ${categoryStyles.categoryPaddingBottom}`}
          >
            <Link href={`/${category}/${item.slug}`}>
              <div className={styles.relativeEle}>
                <Image
                  src={item.featuredImage.node.sourceUrl}
                  alt={item.title}
                  sizes="500px"
                  fill
                  priority={true}
                />
                <div className={styles.hoverOverlay}></div>
              </div>
            </Link>
            <div className={categoryStyles.flexCategory}>
              <Link href={`/${category}/${item.slug}`}>{item.title}</Link>
              <span>{item.date}</span>
              <div
                className={styles.dangerHtml}
                dangerouslySetInnerHTML={{
                  __html: item?.content.slice(0, 190) + " ...",
                }}
              ></div>
            </div>
          </div>
        );
      })}
    </div>
  );
};

export default PostList;

2. 创建客户端交互组件LoadMoreController

用'use client'标记,负责维护状态和处理点击事件:

'use client';

import { useState } from 'react';
import PostList from './PostList';

export default function LoadMoreController({ category }) {
  // 维护展示的文章数量状态
  const [numOfPosts, setNumOfPosts] = useState(2);

  const handleLoadMore = () => {
    setNumOfPosts(prev => prev + 2); // 每次点击增加2篇,可按需调整
  };

  return (
    <div>
      {/* 将状态传递给服务端组件,状态变化时会触发服务端重新取数渲染 */}
      <PostList category={category} numOfPosts={numOfPosts} />
      <button onClick={handleLoadMore}>加载更多</button>
    </div>
  );
}

3. 在页面中使用客户端控制器

替换原来直接引用PostList的方式:

// 页面组件(可服务端可客户端)
import LoadMoreController from './LoadMoreController';

export default function CategoryPage({ params }) {
  return <LoadMoreController category={params.category} />;
}

方案优势

  • 保留服务端组件的核心价值:首屏数据在服务端预取,更快的渲染速度和更好的SEO表现。
  • 仅将必要的交互逻辑放在客户端组件,避免全客户端化导致的性能损耗。
  • 状态变化时,服务端组件会自动重新执行getData并渲染新的列表,无需手动处理数据拼接。

备选方案(全客户端组件)

如果你的场景不需要服务端预取数据,也可以直接把PostList改成客户端组件,用useState+useEffect管理数据:

'use client';

import { useState, useEffect } from 'react';
// 导入其他依赖...

export default function PostList({ category }) {
  const [posts, setPosts] = useState([]);
  const [numOfPosts, setNumOfPosts] = useState(2);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchPosts = async () => {
      setIsLoading(true);
      try {
        // 调用API接口获取数据(需自行实现API路由)
        const res = await fetch(`/api/posts/${category}?num=${numOfPosts}`);
        const data = await res.json();
        setPosts(data);
      } catch (err) {
        console.error('获取文章失败', err);
      } finally {
        setIsLoading(false);
      }
    };
    fetchPosts();
  }, [category, numOfPosts]);

  const handleLoadMore = () => {
    setNumOfPosts(prev => prev + 2);
  };

  // 渲染列表和按钮...
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 05:17:51