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

Next.js 13.4:如何在ISR/SSR组件中实现无限滚动数据获取?

在Next.js 13.4中为ISR/SSR组件实现无限滚动的可行方案

不管是SSR还是ISR组件,核心思路都是服务端渲染初始数据 + 客户端处理滚动加载逻辑——因为无限滚动依赖交互与状态管理,必须结合客户端组件实现。以下是针对两种场景的具体实现方式:

一、SSR组件的无限滚动实现

SSR组件默认是服务器端渲染(无'use client'指令),但滚动交互需要客户端逻辑,所以采用「服务器组件传初始数据 + 客户端组件处理加载」的组合模式:

1. 服务器组件渲染初始数据

比如在app/posts/page.tsx中:

import PostsList from './PostsList';

// 封装接口请求,SSR模式下关闭缓存
async function fetchPosts(page: number = 1, limit: number = 10) {
  const res = await fetch(`https://your-api.com/posts?page=${page}&limit=${limit}`, {
    next: { revalidate: 0 } // 强制SSR,不缓存请求结果
  });
  return res.json();
}

export default async function PostsPage() {
  // 服务端预获取第一页数据
  const initialPosts = await fetchPosts();
  return <PostsList initialPosts={initialPosts} />;
}

2. 客户端组件处理滚动加载

创建PostsList.tsx(添加'use client'指令):

'use client';

import { useState, useEffect, useRef } from 'react';

interface Post {
  id: number;
  title: string;
}

interface PostsListProps {
  initialPosts: Post[];
}

export default function PostsList({ initialPosts }: PostsListProps) {
  const [posts, setPosts] = useState<Post[]>(initialPosts);
  const [currentPage, setCurrentPage] = useState(2);
  const [isLoading, setIsLoading] = useState(false);
  // 用于监听滚动触底的元素
  const loaderRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // 使用Intersection Observer监听触底事件
    const observer = new IntersectionObserver(
      (entries) => {
        const target = entries[0];
        if (target.isIntersecting && !isLoading) {
          loadMore();
        }
      },
      { rootMargin: '200px' } // 提前200px触发加载
    );

    if (loaderRef.current) observer.observe(loaderRef.current);
    return () => loaderRef.current && observer.unobserve(loaderRef.current);
  }, [isLoading]);

  async function loadMore() {
    setIsLoading(true);
    try {
      // 客户端请求下一页数据(可直接调外部API或内部API路由)
      const res = await fetch(`https://your-api.com/posts?page=${currentPage}&limit=10`);
      const newPosts = await res.json();
      
      if (newPosts.length > 0) {
        setPosts(prev => [...prev, ...newPosts]);
        setCurrentPage(prev => prev + 1);
      }
    } catch (err) {
      console.error('加载失败:', err);
    } finally {
      setIsLoading(false);
    }
  }

  return (
    <div className="posts-container">
      <ul>
        {posts.map(post => (
          <li key={post.id} className="post-item">{post.title}</li>
        ))}
      </ul>
      <div ref={loaderRef} className="loader">
        {isLoading ? '加载中...' : '滚动加载更多'}
      </div>
    </div>
  );
}

二、ISR组件的无限滚动实现

ISR的核心是静态生成+增量更新,初始数据静态缓存,后续加载的分页数据也可以通过ISR策略缓存,减少服务器压力:

1. 服务器组件生成初始静态数据

在app/posts-isr/page.tsx中:

import PostsListISR from './PostsListISR';

async function fetchPosts(page: number = 1, limit: number = 10) {
  const res = await fetch(`https://your-api.com/posts?page=${page}&limit=${limit}`, {
    next: { revalidate: 60 } // ISR缓存60秒,到期自动更新
  });
  return res.json();
}

export default async function PostsISRPage() {
  const initialPosts = await fetchPosts();
  return <PostsListISR initialPosts={initialPosts} />;
}

2. 用ISR API路由处理后续请求

创建app/api/posts-isr/route.ts,让分页请求也走ISR缓存:

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const page = searchParams.get('page') || '1';
  const limit = searchParams.get('limit') || '10';

  const res = await fetch(`https://your-api.com/posts?page=${page}&limit=${limit}`, {
    next: { revalidate: 60 } // 分页数据同样缓存60秒
  });
  const posts = await res.json();

  return Response.json(posts);
}

3. 客户端组件请求ISR API

修改PostsListISR.tsx中的loadMore函数,请求上述ISR API:

async function loadMore() {
  setIsLoading(true);
  try {
    const res = await fetch(`/api/posts-isr?page=${currentPage}&limit=10`);
    const newPosts = await res.json();
    
    if (newPosts.length > 0) {
      setPosts(prev => [...prev, ...newPosts]);
      setCurrentPage(prev => prev + 1);
    }
  } catch (err) {
    console.error('加载失败:', err);
  } finally {
    setIsLoading(false);
  }
}

关键注意事项

  • 客户端组件限制:所有涉及状态、事件监听的逻辑必须放在带'use client'指令的组件中,服务器组件仅负责数据预取。
  • 防重复请求:必须通过isLoading状态阻止滚动触发多次并行请求。
  • ISR缓存策略:根据业务需求调整revalidate时间,平衡数据新鲜度与缓存性能。
  • 边界处理:添加无更多数据的提示,以及加载失败的重试逻辑,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 08:57:21