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

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路由,获取分页/筛选数据后更新组件状态。这种方式避免了页面跳转,性能开销更小。

实现示例:

  1. 创建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
    }
  });
}
  1. 页面组件代码:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 13:29:56