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
相关产品推荐
相关产品推荐

