Parámetros de búsqueda
Lee y manipula parámetros de consulta de URL con useSearchParams (client) y la prop searchParams (server).
Busca en todas las páginas de la documentación
Lee y manipula parámetros de consulta de URL con useSearchParams (client) y la prop searchParams (server).
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de referencia rápida -- lista para copiar y pegar.
// Server Component -- accede a searchParams como una prop (Promise en Next.js 15+)
type Props = { searchParams: Promise<{ q?: string; page?: string }> };
export default async function Page({ searchParams }: Props) {
const { q, page } = await searchParams;
const results = await search(q, Number(page) || 1);
return <ResultsList results={results} />;
}// Client Component -- hook useSearchParams
"use client";
import { useSearchParams, useRouter, usePathname } from "next/navigation";
export function SearchInput() {
const searchParams = useSearchParams();
const router = useRouter();
const pathname = usePathname();
function handleSearch(term: string) {
const params = new URLSearchParams(searchParams.toString());
if (term) params.set("q", term);
else params.delete("q");
router.push(`${pathname}?${params.toString()}`);
}
return (
<input
defaultValue={searchParams.get("q") ?? ""}
onChange={(e) => handleSearch(e.target.value)}
placeholder="Buscar..."
/>
);
}Cuándo usarlo: Necesitas estado impulsado por URL -- filtros de búsqueda, paginación, ordenamiento -- que deban ser compartibles via URL y sobrevivir a las recargas de página.
// app/products/page.tsx (Server Component)
import { Suspense } from "react";
import { SearchBar } from "./search-bar";
import { ProductGrid } from "./product-grid";
import { Pagination } from "./pagination";
type Props = {
searchParams: Promise<{
q?: string;
category?: string;
sort?: string;
page?: string;
}>;
};
export default async function ProductsPage({ searchParams }: Props) {
const { q, category, sort, page } = await searchParams;
return (
<main className="max-w-6xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Productos</h1>
<Suspense fallback={<div>Cargando búsqueda...</div>}>
<SearchBar />
</Suspense>
<Suspense
key={`${q}-${category}-${sort}-${page}`}
fallback={<div>Cargando productos...</div>}
>
<ProductResults
query={q}
category={category}
sort={sort ?? "newest"}
page={Number(page) || 1}
/>
</Suspense>
</main>
);
}
async function ProductResults({
query,
category,
sort,
page,
}: {
query?: string;
category?: string;
sort: string;
page: number;
}) {
const { products, totalPages } = await fetchProducts({
query,
category,
sort,
page,
});
return (
<>
<ProductGrid products={products} />
<Pagination currentPage={page} totalPages={totalPages} />
</>
);
}// app/products/search-bar.tsx
"use client";
import { useSearchParams, usePathname, useRouter } from "next/navigation";
import { useTransition } from "react";
import { useDebouncedCallback } from "use-debounce";
export function SearchBar() {
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const [isPending, startTransition] = useTransition();
const handleSearch = useDebouncedCallback((term: string) => {
const params = new URLSearchParams(searchParams.toString());
if (term) {
params.set("q", term);
params.set("page", "1"); // resetea a página 1 en búsqueda nueva
} else {
params.delete("q");
}
startTransition(() => {
router.push(`${pathname}?${params.toString()}`);
});
}, 300);
return (
<div className="relative mb-6">
<input
type="search"
defaultValue={searchParams.get("q") ?? ""}
onChange={(e) => handleSearch(e.target.value)}
placeholder="Buscar productos..."
className="w-full border rounded px-4 py-2"
/>
{isPending && (
<span className="absolute right-3 top-2.5 text-gray-400">
Buscando...
</span>
)}
</div>
);
}// app/products/pagination.tsx
"use client";
import { useSearchParams, usePathname } from "next/navigation";
import Link from "next/link";
export function Pagination({
currentPage,
totalPages,
}: {
currentPage: number;
totalPages: number;
}) {
const searchParams = useSearchParams();
const pathname = usePathname();
function createPageUrl(page: number) {
const params = new URLSearchParams(searchParams.toString());
params.set("page", page.toString());
return `${pathname}?${params.toString()}`;
}
return (
<div className="flex gap-2 mt-6">
{currentPage > 1 && (
<Link href={createPageUrl(currentPage - 1)} className="px-3 py-1 border rounded">
Anterior
</Link>
)}
<span className="px-3 py-1">
Página {currentPage} de {totalPages}
</span>
{currentPage < totalPages && (
<Link href={createPageUrl(currentPage + 1)} className="px-3 py-1 border rounded">
Siguiente
</Link>
)}
</div>
);
}Lo que demuestra:
searchParams en el servidor como una Promise (patrón Next.js 15+)useSearchParams en el cliente para leer y actualizar parámetros de consultakey en <Suspense> para volver a triggerear estados de carga en cambios de parámetrossearchParams del lado del servidor: En el App Router, los componentes de página reciben searchParams como una prop. En Next.js 15+, esto es una Promise que debe ser esperada. Acceder a searchParams activa el renderizado dinámico de la ruta.useSearchParams del lado del cliente: Devuelve una instancia URLSearchParams de solo lectura. Para actualizar, construye un nuevo URLSearchParams, modifícalo, y presiona con router.push() o router.replace().useSearchParams requiere un límite de Suspense -- Durante el prerendering estático, los parámetros de búsqueda no están disponibles. Envolviendo el componente en <Suspense> proporciona un fallback mientras los parámetros se hidratan en el cliente.router.push crea una nueva entrada de historial; router.replace reemplaza la entrada actual (mejor para filtros y ordenamiento).Usando router.replace para evitar contaminar el historial:
// Bueno para filtros -- el usuario aún puede usar el botón atrás de forma significativa
startTransition(() => {
router.replace(`${pathname}?${params.toString()}`);
});Leyendo un parámetro único con un valor por defecto:
const sort = searchParams.get("sort") ?? "newest";
const page = Number(searchParams.get("page")) || 1;Parámetros multivalor (arrays):
// URL: ?color=red&color=blue
const colors = searchParams.getAll("color"); // ["red", "blue"]
// Configurando múltiples valores
const params = new URLSearchParams();
["red", "blue"].forEach((c) => params.append("color", c));// Tipo searchParams para Server Component (Next.js 15+)
type PageProps = {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};
// Helper de parámetros de búsqueda tipado
type ProductFilters = {
q?: string;
category?: string;
sort?: "newest" | "price-asc" | "price-desc";
page?: string;
};
type Props = { searchParams: Promise<ProductFilters> };searchParams activa el renderizado dinámico de la ruta -- Cualquier página que lea searchParams en el servidor no puede ser generada estáticamente. Solución: Si quieres una página estática con filtrado del lado del cliente, lee los parámetros solo en Client Components con useSearchParams.
useSearchParams sin Suspense causa errores de construcción -- Durante el prerendering, useSearchParams lanza una excepción porque no hay parámetros para leer. Solución: Siempre envuelve componentes que usan useSearchParams en un límite <Suspense>.
Cierre estancado con searchParams -- En un Client Component, la referencia searchParams de useSearchParams() se actualiza en la navegación, pero los cierres en manejadores de eventos pueden capturar el valor anterior. Solución: Lee searchParams en el momento de la llamada dentro del manejador, no fuera de él.
Los parámetros de array pierden seguridad de tipo -- searchParams.get("color") devuelve solo el primer valor incluso cuando existen múltiples. Solución: Usa searchParams.getAll("color") para parámetros multivalor.
searchParams es una Promise en Next.js 15+ -- Desestructurarlo directamente sin await te da un objeto Promise. Solución: Siempre const { q } = await searchParams en Server Components.
| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
useState (solo cliente) | El estado es efímero y no necesita sobrevivir a recargas | El estado debe ser compartible via URL |
Segmentos de ruta dinámicos ([slug]) | El parámetro define la identidad del recurso | El parámetro es un filtro o modificador |
| Cookies | El estado debe persistir entre sesiones pero no aparecer en la URL | El estado debe ser visible y compartible |
Librería nuqs | Quieres parámetros de búsqueda con seguridad de tipo y validación | El useSearchParams incorporado es suficiente |
| Zustand con sincronización de URL | Estado multíparámetro complejo con valores derivados | Parámetros de URL simples de clave-valor |
searchParams es una Promise que debe ser esperada antes de acceder a las propiedadesawait te da un objeto Promise, no los valoressearchParams en un Server Component activa el renderizado dinámico de la rutauseSearchParams<Suspense>, la construcción falla porque useSearchParams lanza una excepciónrouter.push crea una nueva entrada de historial del navegador (el usuario puede presionar atrás)router.replace reemplaza la entrada de historial actual (mejor para filtros y ordenamiento)replace cuando los cambios de parámetros frecuentes contaminarían la pila de historial// Leyendo múltiples valores
const colors = searchParams.getAll("color"); // ["red", "blue"]
// Configurando múltiples valores
const params = new URLSearchParams();
["red", "blue"].forEach((c) => params.append("color", c));searchParams.get("color") solo devuelve el primer valorconst handleSearch = (term: string) => {
const params = new URLSearchParams(searchParams.toString());
if (term) {
params.set("q", term);
params.set("page", "1"); // resetea a página 1
} else {
params.delete("q");
}
router.push(`${pathname}?${params.toString()}`);
};type PageProps = {
params: Promise<{ slug: string }>;
searchParams: Promise<{
[key: string]: string | string[] | undefined;
}>;
};string, string[], o undefinedsearchParamssearchParams se actualiza en la navegación, pero el cierre no lo vuelve a capturarsearchParams en el momento de la llamada dentro del manejador para obtener el valor más reciente<Suspense
key={`${q}-${category}-${sort}-${page}`}
fallback={<div>Cargando...</div>}
>
<ProductResults query={q} sort={sort} page={page} />
</Suspense>key desmonta y remonta el límite de Suspense, mostrando el fallback de nuevo[slug]) cuando el parámetro define la identidad del recurso (ej. /products/shoes)searchParams cuando el parámetro es un filtro o modificador (ej. ?sort=price&page=2)type ProductFilters = {
q?: string;
category?: string;
sort?: "newest" | "price-asc" | "price-desc";
page?: string;
};
type Props = { searchParams: Promise<ProductFilters> };useSearchParamsRevisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥