Next.js SSR缓存存储范围及分页排序可扩展方案咨询
一、Cache-Control 缓存的内容
你设置的 Cache-Control 会缓存当前请求对应的切片后数据,也就是对应某一页的6条产品数据,而非拉取的全部API数据。
因为每个请求的 page 参数不同,对应的请求URL(比如 /products?page=1 和 /products?page=2)是不同的,CDN或服务器会根据完整URL作为缓存键,所以每一页的结果会被单独缓存。但要注意:你的当前代码中,每次执行 getServerSideProps 都会重新调用 getDataFromApi() 拉取全部数据,缓存只作用于最终返回的页面/ props,并没有减少对上游API的请求量。
二、针对大数量级数据的可扩展方案
针对数据量达数千条的场景,结合你的排序需求(推荐产品在前,各自按字母排序),可以从以下几个方向优化:
1. 缓存全量排序后的产品数据
核心思路是避免每次请求都重复拉取上游API、重复排序,而是把处理好的全量排序数据缓存起来,后续所有分页请求直接从缓存取数据切片。
示例代码(用内存缓存,生产环境建议用Redis等分布式缓存):
// 全局缓存变量,生产环境建议用Redis替代 let cachedSortedProducts = null; let cacheExpiry = 0; export const getServerSideProps = async (ctx) => { ctx.res.setHeader( 'Cache-Control', 'public, s-maxage=60, stale-while-revalidate=120' ); const page = parseInt(ctx.query.page) || 1; const itemsPerPage = 6; const cacheTTL = 3600; // 缓存1小时 // 检查缓存是否有效 if (!cachedSortedProducts || Date.now() > cacheExpiry) { // 仅缓存过期时才调用API const products = await getDataFromApi(); const featured = products.filter(isFeatured).sort(alphabetical); const notFeatured = products.filter(isNotFeatured).sort(alphabetical); cachedSortedProducts = [...featured, ...notFeatured]; cacheExpiry = Date.now() + cacheTTL * 1000; } // 从缓存数据中切片分页 const startIndex = (page - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const paginatedProducts = cachedSortedProducts.slice(startIndex, endIndex); return { props: { products: paginatedProducts } }; };
2. 利用上游API的排序能力减少服务端处理
既然上游API支持单字段排序,可以拆分请求:
- 先调用API拉取所有
featured=true的产品,要求按字母排序(用API支持的排序参数) - 再调用API拉取所有
featured=false的产品,同样按字母排序 - 将这两部分结果合并后缓存,后续分页直接从合并后的缓存数据切片
这样可以避免在服务端对全量数据做filter和sort,减少服务端的CPU和内存消耗,同时拆分的API请求也可能比拉全量更快。
3. 切换到增量静态生成(ISR)
如果产品数据不是实时更新的(比如更新频率低于1小时),可以用Next.js的ISR预先生成所有分页页面,彻底避免服务器端每次请求都处理数据:
export const getStaticPaths = async () => { // 先拉取全量数据计算总页数 const products = await getDataFromApi(); const featured = products.filter(isFeatured).sort(alphabetical); const notFeatured = products.filter(isNotFeatured).sort(alphabetical); const sortedProducts = [...featured, ...notFeatured]; const totalPages = Math.ceil(sortedProducts.length / 6); // 生成所有分页路径 const paths = Array.from({ length: totalPages }, (_, i) => ({ params: { page: (i + 1).toString() } })); return { paths, fallback: 'blocking' }; }; export const getStaticProps = async ({ params }) => { const page = parseInt(params.page); const itemsPerPage = 6; const products = await getDataFromApi(); const featured = products.filter(isFeatured).sort(alphabetical); const notFeatured = products.filter(isNotFeatured).sort(alphabetical); const sortedProducts = [...featured, ...notFeatured]; const startIndex = (page - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const paginatedProducts = sortedProducts.slice(startIndex, endIndex); return { props: { products: paginatedProducts }, revalidate: 3600 // 每小时重新生成页面 }; };
4. 客户端缓存优化方案
如果担心服务端压力,也可以回到客户端拉取方案,但用缓存库(如React Query)优化:
- 首次加载时拉取全量产品数据,用React Query缓存到内存+本地存储
- 客户端直接在缓存数据上执行排序和分页逻辑
- 后续切换页面无需再请求API,直接从缓存取数据处理
数千条数据的体积通常在几MB级别,对现代浏览器来说完全可承受,且React Query会自动处理缓存过期、后台刷新等逻辑,用户体验也能得到保障。
内容的提问来源于stack exchange,提问作者muhammadarifftaha

