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

Next.js分页时如何保留URL参数?附实现代码

Next.js分页切换保留URL筛选/搜索参数的实现方案

问题场景

开发的Next.js应用带有筛选和搜索功能,但切换分页时,URL中的搜索/筛选参数会被清除,仅保留?page=#ofPage。需要实现分页切换时保留这些参数以提升用户体验,同时咨询是否可通过query-string库实现及具体做法。

尝试过程

最初尝试通过usePathname、useSearchParams获取当前路径与参数,构造${pathname}?${params.toString()}作为Link组件的路径,但出现URL同时包含当前页和目标页参数的问题,后续发现使用.set方法移除当前页参数可解决该冲突。

最终解决方案:使用query-string实现

最终通过query-string库成功实现需求,以下是前后代码对比:

原代码(存在参数丢失问题)

import Link from "next/link";
import React from "react";
import { Button } from "../ui/button";
import { Card, CardContent } from "../ui/card";
import { cn } from "@/lib/utils";
import { ChevronLeft, ChevronRight, Ellipsis } from "lucide-react";
import { usePathname, useSearchParams } from "next/navigation";

type Props = {
  currentPage: number;
  count: number;
  perPage: number;
};

const Pagination = async ({ currentPage, count, perPage }: Props) => {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const params = new URLSearchParams(searchParams);
  const totalPages = Math.ceil(count / perPage);
  const pages = [];
  const offset = 1;
  for (let i = currentPage - offset; i <= currentPage + offset; i++) {
    if (i >= 1 && i <= totalPages) {
      pages.push(i);
    }
  }
  await new Promise((resolve) => {
    setTimeout(() => {
      resolve(undefined);
    });
  });
  if (totalPages === 1) return null;
  return (
    <div className="px-[2.5%]">
      <Card className=" w-fit rounded-xl mx-auto">
        <CardContent className="flex items-center justify-center gap-2 p-2 ">
          {currentPage > 1 ? (
            <Link
              href={{
                pathname: "/components",
                query: {
                  page: currentPage - 1,
                },
              }}
            >
              <Button variant="ghost" className="w-10 h-10">
                <ChevronLeft className="w-5 h-5 shrink-0" />
              </Button>
            </Link>
          ) : null}
          {typeof count === "number" && (
            <div className="flex items-center gap-2">
              {pages.map((page, idx) => {
                return (
                  <Link
                    key={idx}
                    href={{
                      pathname: "/components",
                      query: {
                        page,
                      },
                    }}
                  >
                    <Button
                      variant="outline"
                      className={cn(
                        "w-10 h-10 flex items-center justify-center rounded-lg",
                        page === currentPage
                          ? "bg-accent border-accent"
                          : "bg-transparent"
                      )}
                      size="sm"
                    >
                      {page}
                    </Button>
                  </Link>
                );
              })}
            </div>
          )}
          {currentPage < Math.ceil(count / perPage) ? (
            <div className="flex items-center gap-2">
              <Button
                variant="ghost"
                className="w-10 h-10 hover:bg-transparent cursor-default"
              >
                <Ellipsis className="w-5 h-5 shrink-0" />
              </Button>
              <Link
                href={{
                  query: {
                    page: currentPage + 1,
                  },
                }}
              >
                <Button variant="ghost" className="w-10 h-10">
                  <ChevronRight className="w-5 h-5 shrink-0" />
                </Button>
              </Link>
            </div>
          ) : null}
        </CardContent>
      </Card>
    </div>
  );
};

export default Pagination;

修改后代码(使用query-string保留参数)

"use client";
import qs from "query-string";
import React from "react";
import { Button } from "../ui/button";
import { Card, CardContent } from "../ui/card";
import { cn } from "@/lib/utils";
import { ChevronLeft, ChevronRight, Ellipsis } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";

type Props = {
  currentPage: number;
  count: number;
  perPage: number;
};

export const Pagination = ({ currentPage, count, perPage }: Props) => {
  const searchParams = useSearchParams();
  const router = useRouter();
  const selectedValue = searchParams.get("page");
  
  const onClick = (slug: number | undefined) => {
    // 解析当前所有URL参数
    const currentPath = qs.parse(searchParams.toString());
    // 保留原有参数,仅更新page值
    const query = {
      ...currentPath,
      page: slug,
    };

    // 构造新URL,自动处理参数拼接与格式
    const url = qs.stringifyUrl(
      {
        url: window.location.href,
        query,
      },
      { skipNull: true }
    );
    router.push(url);
  };

  const totalPages = Math.ceil(count / perPage);
  const pages = [];
  const offset = 1;
  for (let i = currentPage - offset; i <= currentPage + offset; i++) {
    if (i >= 1 && i <= totalPages) {
      pages.push(i);
    }
  }
  if (totalPages === 1) return null;
  return (
    <div className="px-[2.5%]">
      <Card className=" w-fit rounded-xl mx-auto">
        <CardContent className="flex items-center justify-center gap-2 p-2 ">
          {currentPage > 1 ? (
            <Button
              variant="ghost"
              className="w-10 h-10"
              onClick={() => onClick(currentPage - 1)}
            >
              <ChevronLeft className="w-5 h-5 shrink-0" />
            </Button>
          ) : null}
          {typeof count === "number" && (
            <div className="flex items-center gap-2">
              {pages.map((page, idx) => {
                return (
                  <Button
                    key={idx}
                    variant="outline"
                    className={cn(
                      "w-10 h-10 flex items-center justify-center rounded-lg",
                      page.toString() === currentPage.toString()
                        ? "bg-accent border-accent"
                        : "bg-transparent"
                    )}
                    size="sm"
                    onClick={() => onClick(page)}
                  >
                    {page}
                  </Button>
                );
              })}
            </div>
          )}
          {currentPage < Math.ceil(count / perPage) ? (
            <div className="flex items-center gap-2">
              <Button
                variant="ghost"
                className="w-10 h-10 hover:bg-transparent cursor-default"
              >
                <Ellipsis className="w-5 h-5 shrink-0" />
              </Button>
              <Button
                variant="ghost"
                className="w-10 h-10"
                onClick={() => onClick(currentPage + 1)}
              >
                <ChevronRight className="w-5 h-5 shrink-0" />
              </Button>
            </div>
          ) : null}
        </CardContent>
      </Card>
    </div>
  );
};

核心实现逻辑

  1. 引入query-string库,简化URL参数的解析与拼接操作
  2. 点击分页按钮时,先解析当前URL的所有参数,保留筛选、搜索等原有参数
  3. 仅替换page参数值,避免清除其他关键参数
  4. 使用qs.stringifyUrl自动处理URL格式,避免重复参数或格式错误
  5. 通过router.push跳转,确保URL参数完整保留

内容的提问来源于stack exchange,提问作者Jean Mako

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 23:33:10