Next.js传统路由下分页与筛选实现方案及性能优化咨询
Next.js 传统页面路由分页/筛选实现方案
在Next.js传统页面路由中实现分页/筛选,既不想用SSG全量拉取后在客户端处理,又要点击按钮即时更新内容,以下是两个高效的实现方案:
方案一:结合 useRouter + getServerSideProps 实现服务端数据即时更新
核心思路是用Next.js的useRouter修改查询参数,触发getServerSideProps重新执行,从而获取最新的分页/筛选数据,无需手动刷新页面。
实现示例:
import { useRouter } from 'next/router'; import { useState } from 'react'; export default function ProductsPage({ products, pagination }) { const router = useRouter(); const [filters, setFilters] = useState({ page: router.query.page || '1', category: router.query.category || 'all' }); // 处理分页/筛选按钮点击 const handleFilterChange = (newFilters) => { const updatedFilters = { ...filters, ...newFilters }; setFilters(updatedFilters); // 更新查询参数,触发getServerSideProps重新执行 router.push({ pathname: '/products', query: updatedFilters }, undefined, { shallow: false }); // shallow设为false,确保服务端重新拉取数据 }; return ( <div> <div className="filters"> <button onClick={() => handleFilterChange({ category: 'electronics' })}> 电子产品 </button> <button onClick={() => handleFilterChange({ category: 'clothing' })}> 服装 </button> </div> <div className="pagination"> <button disabled={pagination.currentPage === 1} onClick={() => handleFilterChange({ page: String(pagination.currentPage - 1) })} > 上一页 </button> <span>第 {pagination.currentPage} 页 / 共 {pagination.totalPages} 页</span> <button disabled={pagination.currentPage === pagination.totalPages} onClick={() => handleFilterChange({ page: String(pagination.currentPage + 1) })} > 下一页 </button> </div> <ul className="product-list"> {products.map(product => ( <li key={product.id}>{product.name} - ¥{product.price}</li> ))} </ul> </div> ); } // 服务端获取数据 export async function getServerSideProps(context) { const { page = 1, category = 'all' } = context.query; // 调用后端接口获取对应分页/筛选的数据 const res = await fetch(`https://your-api-domain.com/products?page=${page}&category=${category}`); const data = await res.json(); return { props: { products: data.products, pagination: { currentPage: parseInt(page), totalPages: data.totalPages } } }; }
关键说明:
- 使用
router.push更新查询参数时,shallow必须设为false,否则Next.js只会更新客户端路由,不会触发getServerSideProps重新执行。 getServerSideProps通过context.query获取最新参数,保证每次路由变化都能拉取对应数据。
方案二:客户端请求API路由(轻量化交互首选)
如果不需要服务端渲染页面内容,或者想要无跳转的流畅交互,可以直接在客户端调用Next.js的API路由,获取分页/筛选数据后更新组件状态。这种方式避免了页面跳转,性能开销更小。
实现示例:
- 创建API路由文件
pages/api/products.js:
export default async function handler(req, res) { const { page = 1, category = 'all' } = req.query; // 这里模拟从数据库/第三方接口获取数据的逻辑 const mockProducts = { electronics: [{ id: 1, name: '智能手机', price: 3999 }, { id: 2, name: '无线耳机', price: 299 }], clothing: [{ id: 3, name: '纯棉T恤', price: 99 }, { id: 4, name: '休闲长裤', price: 199 }], all: [{ id: 1, name: '智能手机', price: 3999 }, { id: 2, name: '无线耳机', price: 299 }, { id: 3, name: '纯棉T恤', price: 99 }, { id: 4, name: '休闲长裤', price: 199 }] }; const targetProducts = mockProducts[category]; const itemsPerPage = 2; const totalPages = Math.ceil(targetProducts.length / itemsPerPage); const startIndex = (page - 1) * itemsPerPage; const paginatedProducts = targetProducts.slice(startIndex, startIndex + itemsPerPage); res.status(200).json({ products: paginatedProducts, pagination: { currentPage: parseInt(page), totalPages } }); }
- 页面组件代码:
import { useState, useEffect } from 'react'; export default function ProductsPage() { const [products, setProducts] = useState([]); const [pagination, setPagination] = useState({ currentPage: 1, totalPages: 1 }); const [filters, setFilters] = useState({ page: 1, category: 'all' }); const [loading, setLoading] = useState(false); // 根据当前筛选条件拉取数据 const fetchProducts = async () => { setLoading(true); const res = await fetch(`/api/products?page=${filters.page}&category=${filters.category}`); const data = await res.json(); setProducts(data.products); setPagination(data.pagination); setLoading(false); }; // 初始化或筛选条件变化时重新拉取数据 useEffect(() => { fetchProducts(); }, [filters]); // 处理筛选/分页变化 const handleFilterChange = (newFilters) => { setFilters(prev => ({ ...prev, ...newFilters })); }; return ( <div> <div className="filters"> <button onClick={() => handleFilterChange({ category: 'electronics', page: 1 })}> 电子产品 </button> <button onClick={() => handleFilterChange({ category: 'clothing', page: 1 })}> 服装 </button> <button onClick={() => handleFilterChange({ category: 'all', page: 1 })}> 全部商品 </button> </div> <div className="pagination"> <button disabled={pagination.currentPage === 1 || loading} onClick={() => handleFilterChange({ page: pagination.currentPage - 1 })} > 上一页 </button> <span>第 {pagination.currentPage} 页 / 共 {pagination.totalPages} 页</span> <button disabled={pagination.currentPage === pagination.totalPages || loading} onClick={() => handleFilterChange({ page: pagination.currentPage + 1 })} > 下一页 </button> </div> {loading ? ( <p>加载中...</p> ) : ( <ul className="product-list"> {products.map(product => ( <li key={product.id}>{product.name} - ¥{product.price}</li> ))} </ul> )} </div> ); }
关键说明:
- 完全在客户端处理数据请求,无页面跳转,交互更流畅。
- 适合不需要SEO支持、追求轻量化交互的场景,仅请求数据而非整个页面,性能更优。
方案选择建议
- 若需要页面内容支持SEO,优先选择方案一,
getServerSideProps会在服务端渲染页面内容。 - 若无需SEO、追求流畅交互体验,方案二更合适。
内容的提问来源于stack exchange,提问作者Imran Kabir
相关产品推荐
相关产品推荐

