Пошук уроків, статей та іншого контенту
Навчитеся читати, змінювати й передавати query-параметри під час навігації в App Router.
Query-параметри — це частина URL після символу ?. Вони використовуються для передавання додаткових даних під час навігації:
/products?q=keyboard&page=2У цьому URL:
q — query-параметр зі значенням keyboard;
page — query-параметр зі значенням 2;
параметри розділені символом &.
Query-параметри зручно використовувати для:
пошуку;
фільтрації;
сортування;
пагінації;
збереження стану сторінки в URL.
В App Router query-параметри не є динамічними сегментами шляху. Наприклад:
/products/keyboardвикористовує динамічний сегмент маршруту, а:
/products?q=keyboardвикористовує query-параметр.
У файлі page.tsx query-параметри доступні через властивість searchParams пропсів сторінки.
У сучасних версіях Next.js searchParams є асинхронним значенням, тому його потрібно отримати через await.
type SearchParams = Promise<{
q?: string;
page?: string;
}>;
type ProductsPageProps = {
searchParams: SearchParams;
};
export default async function ProductsPage({
searchParams,
}: ProductsPageProps) {
const params = await searchParams;
const query = params.q ?? "";
const page = Number(params.page ?? "1");
return (
<main>
<h1>Товари</h1>
<p>Пошуковий запит: {query || "не задано"}</p>
<p>Сторінка: {Number.isNaN(page) ? 1 : page}</p>
</main>
);
}Для URL:
/products?q=keyboard&page=2компонент отримає приблизно такий об’єкт:
{
q: "keyboard",
page: "2"
}Усі значення query-параметрів спочатку є рядками. Якщо значення потрібно використовувати як число, його необхідно перетворити самостійно.
const page = Number(params.page ?? "1");Якщо параметра немає, його значення буде undefined:
const query = params.q ?? "";Оператор ?? дозволяє задати значення за замовчуванням.
Один параметр може повторюватися:
/products?tag=books&tag=electronicsУ такому випадку значення може бути масивом:
type SearchParams = Promise<{
tag?: string | string[];
}>;
export default async function ProductsPage({
searchParams,
}: {
searchParams: SearchParams;
}) {
const params = await searchParams;
const tags = params.tag
? Array.isArray(params.tag)
? params.tag
: [params.tag]
: [];
return (
<main>
<h1>Вибрані категорії</h1>
<ul>
{tags.map((tag) => (
<li key={tag}>{tag}</li>
))}
</ul>
</main>
);
}Точний тип searchParams краще описувати відповідно до параметрів, які підтримує сторінка.
У Client Component для читання параметрів використовується хук useSearchParams з пакета next/navigation.
"use client";
import { useSearchParams } from "next/navigation";
export default function SearchStatus() {
const searchParams = useSearchParams();
const query = searchParams.get("q");
const page = searchParams.get("page");
return (
<p>
Запит: {query ?? "не задано"}, сторінка: {page ?? "1"}
</p>
);
}useSearchParams повертає об’єкт, сумісний з URLSearchParams.
Основні методи:
get("name") — отримати перше значення параметра;
getAll("name") — отримати всі значення параметра;
has("name") — перевірити наявність параметра;
toString() — отримати query-рядок без символу ?.
Наприклад:
const searchParams = useSearchParams();
const tags = searchParams.getAll("tag");
const hasSort = searchParams.has("sort");
const queryString = searchParams.toString();Якщо параметр відсутній, get повертає null.
Suspense для useSearchParamsЯкщо сторінка статично генерується, використання useSearchParams у Client Component потрібно ізолювати в межах Suspense.
import { Suspense } from "react";
import SearchStatus from "./SearchStatus";
export default function ProductsPage() {
return (
<main>
<h1>Товари</h1>
<Suspense fallback={<p>Завантаження параметрів...</p>}>
<SearchStatus />
</Suspense>
</main>
);
}Це дозволяє Next.js окремо завантажити частину інтерфейсу, яка залежить від URL.
LinkДля навігації між сторінками використовуйте компонент Link.
import Link from "next/link";
export default function ProductNavigation() {
return (
<nav>
<Link href="/products?q=keyboard">
Клавіатури
</Link>
<Link href="/products?q=mouse&page=2">
Миші, сторінка 2
</Link>
</nav>
);
}Після переходу сторінка /products отримає відповідні значення через searchParams.
Query-параметри також можна передавати як об’єкт у href:
import Link from "next/link";
export default function ProductNavigation() {
return (
<Link
href={{
pathname: "/products",
query: {
q: "keyboard",
page: "2",
},
}}
>
Знайти клавіатури
</Link>
);
}Next.js сформує URL:
/products?q=keyboard&page=2useRouterУ Client Component для програмної навігації використовується useRouter.
"use client";
import { useRouter } from "next/navigation";
export default function ProductActions() {
const router = useRouter();
function showSecondPage() {
router.push("/products?page=2");
}
return (
<button type="button" onClick={showSecondPage}>
Наступна сторінка
</button>
);
}Основні методи:
router.push(url) — переходить на новий URL і додає його до історії браузера;
router.replace(url) — змінює поточний запис в історії;
router.back() — повертається на попередню сторінку;
router.refresh() — повторно завантажує Server Components для поточного маршруту.
Для фільтрів і пошуку часто зручніше використовувати replace, щоб кожна зміна фільтра не створювала окремий запис в історії браузера.
router.replace("/products?q=keyboard");Під час зміни одного параметра важливо зберігати інші параметри URL.
Наприклад, якщо поточний URL такий:
/products?q=keyboard&sort=price&page=2і потрібно змінити лише page, не слід створювати URL вручну з нуля. Для цього скопіюйте поточні параметри через URLSearchParams.
"use client";
import {
usePathname,
useRouter,
useSearchParams,
} from "next/navigation";
export default function Pagination() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
function goToPage(page: number) {
const params = new URLSearchParams(searchParams.toString());
params.set("page", String(page));
router.push(`${pathname}?${params.toString()}`);
}
return (
<div>
<button type="button" onClick={() => goToPage(1)}>
1
</button>
<button type="button" onClick={() => goToPage(2)}>
2
</button>
<button type="button" onClick={() => goToPage(3)}>
3
</button>
</div>
);
}usePathname повертає шлях без query-рядка:
/productssearchParams.toString() повертає поточні параметри:
q=keyboard&sort=price&page=2У результаті формується URL:
/products?q=keyboard&sort=price&page=3Щоб видалити параметр, використовуйте delete:
const params = new URLSearchParams(searchParams.toString());
params.delete("q");
router.replace(`${pathname}?${params.toString()}`);Якщо після видалення параметрів не залишилося, можна не додавати символ ?:
const queryString = params.toString();
const url = queryString ? `${pathname}?${queryString}` : pathname;
router.replace(url);Структура файлів:
app/
└── products/
├── page.tsx
└── ProductsControls.tsx// app/products/page.tsx
import { Suspense } from "react";
import ProductsControls from "./ProductsControls";
type SearchParams = Promise<{
q?: string;
page?: string;
}>;
type ProductsPageProps = {
searchParams: SearchParams;
};
export default async function ProductsPage({
searchParams,
}: ProductsPageProps) {
const params = await searchParams;
const query = params.q ?? "";
const parsedPage = Number(params.page ?? "1");
const page = Number.isInteger(parsedPage) && parsedPage > 0
? parsedPage
: 1;
return (
<main>
<h1>Товари</h1>
<Suspense fallback={<p>Завантаження...</p>}>
<ProductsControls />
</Suspense>
<p>
Пошук: {query || "усі товари"}
</p>
<p>
Поточна сторінка: {page}
</p>
</main>
);
}// app/products/ProductsControls.tsx
"use client";
import {
FormEvent,
useState,
} from "react";
import {
usePathname,
useRouter,
useSearchParams,
} from "next/navigation";
export default function ProductsControls() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [query, setQuery] = useState(
searchParams.get("q") ?? "",
);
function updateQuery(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const params = new URLSearchParams(searchParams.toString());
if (query.trim()) {
params.set("q", query.trim());
} else {
params.delete("q");
}
// Після зміни пошуку починаємо з першої сторінки.
params.set("page", "1");
const queryString = params.toString();
const url = queryString
? `${pathname}?${queryString}`
: pathname;
router.replace(url);
}
function clearQuery() {
const params = new URLSearchParams(searchParams.toString());
params.delete("q");
params.set("page", "1");
const queryString = params.toString();
const url = queryString
? `${pathname}?${queryString}`
: pathname;
setQuery("");
router.replace(url);
}
return (
<div>
<form onSubmit={updateQuery}>
<label htmlFor="product-query">
Пошук товарів
</label>
<input
id="product-query"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Наприклад, keyboard"
/>
<button type="submit">
Знайти
</button>
</form>
<button type="button" onClick={clearQuery}>
Очистити пошук
</button>
</div>
);
}Як працює цей приклад:
Server Component читає q і page з searchParams.
Client Component читає поточний q через useSearchParams.
Після надсилання форми оновлюється тільки q.
Параметр page скидається до 1.
Інші параметри, які вже були в URL, зберігаються.
router.replace змінює URL без додавання зайвого запису в історію.
Не створюйте query-рядок простою конкатенацією, якщо значення походить від користувача:
// Потенційно проблемний підхід
const url = `/products?q=${query}`;У значенні можуть бути пробіли, символи &, ? або інші спеціальні символи. Використовуйте URLSearchParams:
const params = new URLSearchParams();
params.set("q", query);
const url = `/products?${params.toString()}`;URLSearchParams коректно кодує значення для URL.
searchParams у Client ComponentУ Client Component не можна читати searchParams як пропс сторінки. Використовуйте:
const searchParams = useSearchParams();awaitУ Server Component сучасного App Router потрібно отримати значення searchParams:
const params = await searchParams;Такий код видалить усі поточні параметри:
router.push(`${pathname}?page=2`);Якщо потрібно зберегти наявні параметри, спочатку скопіюйте їх:
const params = new URLSearchParams(searchParams.toString());
params.set("page", "2");
router.push(`${pathname}?${params.toString()}`);Усі query-параметри надходять як рядки:
const page = Number(params.page ?? "1");Потрібно також враховувати некоректні значення:
const parsedPage = Number(params.page ?? "1");
const page = Number.isInteger(parsedPage) && parsedPage > 0
? parsedPage
: 1;push для кожної зміни фільтраЯкщо кожне натискання або введення додає запис через router.push, кнопка «Назад» може вимагати багато натискань. Для заміни фільтрів, пошукового запиту та сортування часто підходить:
router.replace(url);Query-параметри зберігаються в URL після символу ?.
У Server Component сторінки вони доступні через searchParams.
У сучасних версіях Next.js searchParams потрібно отримувати через await.
У Client Component параметри читаються через useSearchParams.
Для навігації з параметрами можна використовувати Link.
Для програмної навігації використовуються useRouter, push і replace.
URLSearchParams допомагає безпечно змінювати, додавати та видаляти параметри.
Під час зміни одного параметра слід зберігати інші параметри поточного URL.
Значення query-параметрів потрібно перевіряти й перетворювати з рядків у потрібні типи.