Гайды

Как сохранить фильтры в URL — чтобы ссылка открывала тот же вид, а «Назад» работал

Иллюстрация: машинка с ручками выбивает их положение дырочками на бумажной ленте

Ты собрал каталог с фильтрами: цвет, размер, сортировка по цене. Всё хранится в состоянии компонента и работает отлично.

Ровно до трёх ситуаций. Отправил другу ссылку «смотри, какие красные» — у него открылся весь каталог. Нажал «Обновить» — фильтры сбросились. Кликнул на товар, вернулся «Назад» — опять всё с нуля.

Причина одна: состояние живёт в памяти вкладки, а ссылка, обновление и «Назад» работают с адресом. Чинится это переносом фильтров в адрес. Ещё в 1999 году Якоб Нильсен писал, что URL — часть интерфейса: его копируют, пересылают и ждут, что он будет открывать то же самое.

Шаг 1. Реши, что кладёшь в адрес, а что нет

Правило простое: в адрес идёт то, что человек захочет переслать или открыть заново.

  • Да: фильтры, сортировка, номер страницы, поисковый запрос, открытая вкладка.
  • Нет: раскрыт ли выпадающий список, наведение мыши, недопечатанный текст, всплывающие окна.
  • Никогда: токены, пароли, личные данные. Адрес видят все — он оседает в истории браузера, в логах сервера и в пересланных сообщениях.

Для примера возьмём два фильтра — color и sort. Целевой адрес выглядит так: /shop?color=red&sort=price.

Шаг 2. Читай состояние из адреса при загрузке

Адрес становится источником правды. Компонент не придумывает начальные значения сам, а берёт их из строки.

const params = new URLSearchParams(location.search);
const color = params.get("color") ?? "all";
const sort = params.get("sort") ?? "popular";

URLSearchParams — встроенный в браузер разборщик строки запроса. get возвращает null, если параметра нет, — тогда подставляем значение по умолчанию.

Не доверяй адресу слепо: его может отредактировать кто угодно. Если пришёл sort=hack, которого у тебя нет, откатывайся к умолчанию, а не падай.

Шаг 3. Записывай в адрес при каждом изменении

Когда человек меняет фильтр, обновляй не только экран, но и строку.

function setFilter(key, value, defaultValue) {
  const params = new URLSearchParams(location.search);
  if (value === defaultValue) params.delete(key);
  else params.set(key, value);
  const query = params.toString();
  history.replaceState(null, "", query ? "?" + query : location.pathname);
}

Две детали внутри:

  • Значения по умолчанию не пиши. Если выбрано «все цвета», параметр удаляется. Адреса выходят короче, а одна и та же выдача не расползается на /shop и /shop?color=all.
  • Не склеивай строку руками. URLSearchParams сам закодирует кириллицу и спецсимволы, а пробел, по MDN, превратит в +. Самодельное "?q=" + text сломается на первом амперсанде в запросе.

Шаг 4. Выбери: replaceState или pushState

Это самое важное решение в инструкции. От него зависит, как работает кнопка «Назад».

  • history.replaceState заменяет текущую запись истории. «Назад» уводит туда, откуда человек пришёл на страницу.
  • history.pushState добавляет новую запись. «Назад» возвращает к прошлому набору фильтров.

Какой брать:

  • Поиск по мере ввода → replace. Иначе «Назад» будет стирать запрос по одной букве.
  • Сортировка, мелкие переключатели → replace. Мало кто считает их отдельным «местом».
  • Категория, вкладка, номер страницы → push. Человек воспринимает это как переход и ждёт, что «Назад» вернёт предыдущий экран.

Если используешь pushState, добавь обработчик возврата. По MDN, событие popstate приходит, когда человек жмёт «Назад» или «Вперёд», а сам вызов pushState его не вызывает.

window.addEventListener("popstate", () => {
  renderFromUrl(); // та же функция, что в шаге 2
});

Шаг 5. Если у тебя React или Next.js

Логика та же, меняются только инструменты.

  • Next.js (App Router). Читать — хуком useSearchParams: он возвращает URLSearchParams только для чтения. Писать — через useRouter: router.push добавляет запись в историю, router.replace — нет. Чтобы страница не прыгала наверх при каждом клике по фильтру, передай { scroll: false }.
  • React Router. Есть хук useSearchParams, который возвращает параметры и функцию setSearchParams для их записи.

Как вообще адрес меняется без перезагрузки страницы, мы разбирали в статье что такое клиентский роутинг.

Шаг 6. Передай фильтры на сервер

Фильтры в адресе — это ещё и готовый запрос к API. Можно отправлять ту же строку как есть:

const res = await fetch("/api/products?" + params.toString());

Сервер разбирает её точно так же. Как связать клиент с сервером целиком, показали в гайде как связать фронтенд и бэкенд, а про поиск по базе — в разборе как добавить поиск в приложение.

Что получится

  • Ссылка /shop?color=red&sort=price у друга открывает ровно тот же экран.
  • «Обновить» ничего не сбрасывает.
  • «Назад» после перехода в товар возвращает к отфильтрованному списку.
  • Выбор категорий через pushState листается кнопками «Назад» и «Вперёд», а набор поиска их не засоряет.

Проверь все четыре пункта руками: скопируй адрес в другое окно, обнови, походи назад-вперёд.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение

Что будет с кириллицей в адресе?

URLSearchParams закодирует её сам: в строке появятся знаки процента, а get вернёт исходный текст. В адресной строке браузер обычно всё равно показывает буквы читаемыми.

Как хранить фильтр, где можно выбрать несколько значений?

Повтори параметр: ?size=42&size=43. Добавляй значения через params.append("size", "43"), а читай все сразу через params.getAll("size") — получишь массив. Не путай с set: по MDN, он удаляет все остальные значения этого ключа.

Можно хранить фильтры в localStorage вместо адреса?

Можно, если нужно «запомнить мой выбор на этом устройстве». Но ссылкой такой вид не поделишься, и в новой вкладке у другого человека он не откроется. Часто делают оба: адрес — главный, localStorage — запасной.

Не повредят ли десятки вариантов адреса SEO?

Могут размыть: поисковик увидит много похожих страниц. Обычно на отфильтрованных вариантах указывают канонический адрес основного каталога, а в индекс пускают только важные сочетания.

Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →