Пошук уроків, статей та іншого контенту
Керуватимете переходами й читанням поточного URL через useRouter, usePathname та useSearchParams.
У Next.js App Router поточний URL можна читати й змінювати за допомогою хуків із next/navigation:
useRouter — програмна навігація;
usePathname — читання шляху поточного URL;
useSearchParams — читання query-параметрів після символу ?.
Усі три хуки призначені для Client Components, тому файл, у якому вони використовуються, має починатися з:
'use client'У Server Components ці хуки використовувати не можна. Параметри маршруту в Server Components передаються через
paramsі
useRouterХук імпортується з next/navigation:
import { useRouter } from 'next/navigation'Він повертає об’єкт із методами навігації:
router.push(url) — переходить на нову адресу та додає її до історії браузера;
router.replace(url) — переходить на нову адресу, але не додає її до історії;
router.back() — повертає на попередню сторінку;
router.forward() — переходить уперед в історії;
router.refresh() — повторно завантажує дані Server Components для поточного маршруту без повного перезавантаження сторінки;
router.prefetch(url) — заздалегідь завантажує маршрут.
push і replacepush варто використовувати, коли користувач очікує повернутися до попереднього стану кнопкою браузера:
router.push('/products?category=books')replace зручний для станів, які не повинні створювати окремий запис в історії. Наприклад, під час зміни фільтрів або сортування:
router.replace('/products?sort=price')Методи не виконують повного перезавантаження документа. Next.js змінює маршрут і завантажує потрібні дані відповідно до нього.
usePathnameusePathname повертає шлях поточного URL без query-параметрів і hash-фрагмента.
import { usePathname } from 'next/navigation'
const pathname = usePathname()Якщо адреса має вигляд:
/products/42?tab=reviews#commentsто значення pathname буде:
/products/42Хук можна використовувати для:
визначення активного пункту навігації;
побудови нового URL на основі поточного шляху;
відображення різного інтерфейсу для різних маршрутів.
Приклад активного пункту меню:
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
export default function Navigation() {
const pathname = usePathname()
return (
<nav>
<Link
href="/"
aria-current={pathname === '/' ? 'page' : undefined}
>
Головна
</Link>
<Link
href="/products"
aria-current={pathname.startsWith('/products') ? 'page' : undefined}
>
Товари
</Link>
</nav>
)
}useSearchParamsuseSearchParams повертає query-параметри поточного URL — частину після ?.
import { useSearchParams } from 'next/navigation'
const searchParams = useSearchParams()Для URL:
/products?category=books&page=2можна отримати значення так:
const category = searchParams.get('category')
const page = searchParams.get('page')Результат:
category === 'books'
page === '2'Значення query-параметрів завжди є рядками. Якщо параметра немає, get повертає null.
searchParams.get('category')Повертає перше значення параметра або null.
searchParams.getAll('tag')Повертає всі значення параметра у вигляді масиву. Це потрібно для URL на кшталт:
/products?tag=frontend&tag=nextjssearchParams.has('page')Перевіряє, чи існує параметр.
searchParams.toString()Повертає query-рядок без символу ?:
category=books&page=2Об’єкт, який повертає useSearchParams, призначений для читання. Щоб змінити параметри, потрібно створити копію через URLSearchParams, змінити її та передати нову адресу в router.push або router.replace.
Розглянемо компонент фільтрів товарів. Він:
читає поточний шлях через usePathname;
читає q і category через useSearchParams;
змінює query-параметри через useRouter;
зберігає інші параметри URL під час оновлення одного фільтра.
app/page.tsximport { Suspense } from 'react'
import ProductFilters from './ProductFilters'
export default function Page() {
return (
<main>
<h1>Каталог товарів</h1>
<Suspense fallback={<p>Завантаження фільтрів...</p>}>
<ProductFilters />
</Suspense>
</main>
)
}app/ProductFilters.tsx'use client'
import { FormEvent, useEffect, useState } from 'react'
import {
usePathname,
useRouter,
useSearchParams,
} from 'next/navigation'
const categories = ['all', 'books', 'electronics']
export default function ProductFilters() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const [query, setQuery] = useState('')
useEffect(() => {
setQuery(searchParams.get('q') ?? '')
}, [searchParams])
function createUrl(changes: Record<string, string | null>) {
const params = new URLSearchParams(searchParams.toString())
for (const [key, value] of Object.entries(changes)) {
if (value) {
params.set(key, value)
} else {
params.delete(key)
}
}
const queryString = params.toString()
return queryString ? `${pathname}?${queryString}` : pathname
}
function handleSearch(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
router.replace(
createUrl({
q: query.trim() || null,
page: '1',
}),
{ scroll: false },
)
}
function handleCategoryChange(category: string) {
router.replace(
createUrl({
category: category === 'all' ? null : category,
page: '1',
}),
{ scroll: false },
)
}
const selectedCategory = searchParams.get('category') ?? 'all'
return (
<section>
<form onSubmit={handleSearch}>
<label htmlFor="search">Пошук</label>
<input
id="search"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Назва товару"
/>
<button type="submit">Знайти</button>
</form>
<div>
<p>Категорія:</p>
{categories.map((category) => (
<button
key={category}
type="button"
onClick={() => handleCategoryChange(category)}
aria-pressed={selectedCategory === category}
>
{category}
</button>
))}
</div>
<p>
Поточний шлях: <strong>{pathname}</strong>
</p>
<p>
Пошук: <strong>{searchParams.get('q') ?? 'не задано'}</strong>
</p>
</section>
)
}Після введення phone та натискання кнопки адреса може стати такою:
/?q=phone&page=1Якщо після цього вибрати категорію electronics, URL зміниться на:
/?q=phone&page=1&category=electronicsФункція createUrl спочатку копіює всі поточні параметри, а потім змінює лише потрібні. Це запобігає випадковому видаленню інших фільтрів.
Не потрібно самостійно кодувати пробіли, амперсанди чи інші спеціальні символи. URLSearchParams зробить це автоматично:
const params = new URLSearchParams()
params.set('q', 'next.js tutorial')
console.log(params.toString())
// q=next.js+tutorialЯкщо значення може містити спеціальні символи, використовуйте URLSearchParams, а не конкатенацію рядків:
const unsafeUrl = `/search?q=${query}`
const params = new URLSearchParams()
params.set('q', query)
const safeUrl = `/search?${params.toString()}`useRouter зручно використовувати після завершення дії:
'use client'
import { useRouter } from 'next/navigation'
export default function CheckoutButton() {
const router = useRouter()
function handleCheckout() {
router.push('/checkout')
}
return (
<button type="button" onClick={handleCheckout}>
Перейти до оформлення
</button>
)
}Для повернення після закриття модального вікна можна використати:
router.back()Якщо користувач відкрив сторінку напряму і повертатися нікуди, краще передбачити запасний маршрут:
function handleClose() {
if (window.history.length > 1) {
router.back()
} else {
router.push('/')
}
}router.refreshrouter.refresh() повторно запитує дані для поточного маршруту та оновлює результат Server Components. Поточний шлях і query-параметри при цьому не змінюються.
Це може бути корисно після дії, яка змінила дані на сервері:
'use client'
import { useRouter } from 'next/navigation'
export default function RefreshButton() {
const router = useRouter()
return (
<button type="button" onClick={() => router.refresh()}>
Оновити дані
</button>
)
}refresh не є аналогом window.location.reload(). Він не перезавантажує весь документ і не скидає стан Client Components так, як повне перезавантаження сторінки.
Suspense для useSearchParamsЯкщо маршрут статично генерується, використання useSearchParams може спричинити перехід частини інтерфейсу на клієнтський рендеринг. У production-збірці Next.js для такого випадку потрібна найближча межа Suspense.
Тому компонент, який використовує useSearchParams, часто обгортають у Server Component:
import { Suspense } from 'react'
import Filters from './Filters'
export default function Page() {
return (
<Suspense fallback={<p>Завантаження...</p>}>
<Filters />
</Suspense>
)
}Сам компонент із хуком залишається Client Component:
'use client'
import { useSearchParams } from 'next/navigation'
export default function Filters() {
const searchParams = useSearchParams()
return <p>{searchParams.get('q') ?? 'Без пошуку'}</p>
}Для App Router потрібно використовувати:
import {
useRouter,
usePathname,
useSearchParams,
} from 'next/navigation'Модуль next/router призначений для Pages Router і не використовується з цими хуками в App Router.
Такий код буде некоректним:
import { usePathname } from 'next/navigation'
export default function Page() {
const pathname = usePathname()
return <p>{pathname}</p>
}Компонент має бути Client Component:
'use client'
import { usePathname } from 'next/navigation'searchParams напрямуuseSearchParams призначений для читання:
searchParams.set('page', '2')Замість цього потрібно створити копію:
const params = new URLSearchParams(searchParams.toString())
params.set('page', '2')
router.push(`${pathname}?${params.toString()}`)Такий код замінює весь query-рядок і видаляє всі параметри, крім page:
router.push(`${pathname}?page=2`)Якщо потрібно зберегти наявні параметри, спочатку скопіюйте їх:
const params = new URLSearchParams(searchParams.toString())
params.set('page', '2')
router.push(`${pathname}?${params.toString()}`)push для кожного введеного символуЯкщо викликати router.push на кожну зміну поля, історія браузера наповниться великою кількістю записів. Для фільтрів зазвичай краще використовувати replace, а навігацію виконувати після відправлення форми або після контрольованої затримки.
? між шляхом і параметрамиПравильний URL має вигляд:
`${pathname}?${params.toString()}`Якщо query-рядок може бути порожнім, не додавайте зайвий символ ?:
const queryString = params.toString()
const url = queryString ? `${pathname}?${queryString}` : pathnameuseRouter використовується для програмної навігації та оновлення маршруту.
router.push додає запис в історію, а router.replace замінює поточний запис.
usePathname повертає шлях без query-параметрів і hash-фрагмента.
useSearchParams читає параметри після ?.
Значення query-параметрів є рядками або null, якщо параметр відсутній.
Для зміни query-параметрів створюйте копію через new URLSearchParams(...).
Усі ці хуки працюють у Client Components.
Компоненти з useSearchParams у статично генерованих маршрутах варто обгортати в Suspense.