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> ); };
核心实现逻辑
- 引入
query-string库,简化URL参数的解析与拼接操作 - 点击分页按钮时,先解析当前URL的所有参数,保留筛选、搜索等原有参数
- 仅替换
page参数值,避免清除其他关键参数 - 使用
qs.stringifyUrl自动处理URL格式,避免重复参数或格式错误 - 通过
router.push跳转,确保URL参数完整保留
内容的提问来源于stack exchange,提问作者Jean Mako
相关产品推荐
相关产品推荐

