Искусство проектирования URL: Роутинг, Query и Hash параметры

от автора

Привет, Хабр! Меня зовут Алексей Фомин, я Technical Lead во Frontend в компании Devs Universe. В своей работе я часто сталкиваюсь с тем, что даже опытные разработчики не всегда задумываются о проектировании URL-структуры приложения, а ведь это критически важный элемент пользовательского опыта, SEO и архитектуры. В этой статье я хочу системно разобрать анатомию URL и дать практические рекомендации по его проектированию.

Содержание

  1. Анатомия URL: из чего он состоит?

  2. Иерархия и структура (Правильное название URL)

  3. Проектирование роутинга (Маршрутизация)

  4. Query Parameters (Параметры строки запроса)

  5. Hash (Фрагмент)

  6. Сводная таблица: что и когда использовать?

  7. Как получить части URL в JavaScript?

  8. Работа с «неидеальными» Query-параметрами

  9. Чеклист правильного проектирования URL

 

Плохой URL — это как кривая вывеска на магазине: он путает пользователей, усложняет навигацию и портит впечатление о всем сайте. Хороший URL, наоборот, понятен, предсказуем и несет смысловую нагрузку.

Анатомия URL: из чего он состоит?

Прежде чем проектировать, давайте разберемся, как URL устроен. Возьмем пример:
https://www.example.com:8080/catalog/search?q=watch&sort=price#product-list

Часть URL

Пример

Описание

Протокол

https:

Правила обмена данными (http, https, ftp)

Доменное имя

www.example.com

Адрес сервера. www — поддомен, example — доменное имя, .com — домен верхнего уровня (TLD)

Порт

:8080

«Дверь» на сервере. По умолчанию для HTTP — 80, для HTTPS — 443 (в URL обычно не отображается)

Путь (Path)

/catalog/search

Иерархический путь к конкретному ресурсу на сервере. Основа для роутинга

Query-параметры

?q=watch&sort=price

Начинаются с ?. Пара ключ=значение, разделенные &. Нужны для параметров страницы (фильтры, поиск, сортировка)

Хэш (Фрагмент)

#product-list

Начинается с #. Якорь внутри страницы. Браузер автоматически прокрутит к элементу с id=»product-list». На сервер не отправляется


Иерархия и структура (Правильное название URL)

URL должен отражать структуру ваших данных, быть читаемым и логичным.

Ключевые принципы:

  • Человеко-понятность (Readable): используйте слова, а не ID там, где это уместно:

    • Плохо: /p/12345;

    • Хорошо: /products/modern-wristwatch;

    • Отлично: /catalog/watches/wrist/modern-wristwatch (показывает иерархию).

  • Иерархичность (Hierarchical): стройте путь от общего к частному, как путь в файловой системе:

    • /{section}/{category}/{subcategory}/{item};

    • Пример: /blog/javascript/frameworks/vue-3-composition-api — сразу понятно, где мы находимся.

  • Множественное число: часто используют множественное число для ресурсов-коллекций:

    • /users/ (список пользователей), /users/annasmith (конкретный пользователь);

    • Это не строгое правило, но распространенная конвенция в RESTful API.

  • Лаконичность: не усложняйте путь без необходимости. Избегайте длинных предложений:

    • Плохо: /site.com/blog/posts/article-about-how-to-build-a-website-in-2024/;

    • Хорошо: /blog/website-development-2024.

  • Постоянство (Persistence): URL — это обещание. Once published, forever available. Не меняйте URL существующих страниц без настройки редиректа со старого адреса на новый (301 Moved Permanently). Иначе вы потеряете пользователей и SEO-вес.

  • Регистр букв: единообразие! Чаще всего используют нижний регистр. Сервера могут быть чувствительны к регистру (Website.com/PAGE и website.com/page могут вести в разные места), что приводит к ошибкам 404.

  • Разделители: для разделения слов в составе пути используйте дефисы (-):

    • Плохо: modern_wristwatch (подчеркивание плохо выделяется в подчеркнутых ссылках);

    • Плохо: modern%20wristwatch (пробел, виден как %20);

    • Хорошо: modern-wristwatch (дефис, читается легко и дружелюбен для SEO).


Проектирование роутинга (Маршрутизация)

Роутинг — это механизм, который сопоставляет URL с кодом, который должен выполниться для обработки запроса.

Подходы:

  1. Серверный роутинг (Traditional): каждый URL ведет на отдельную HTML-страницу, которую генерирует и возвращает сервер. При переходе по ссылке браузер полностью обновляет страницу:

    • Плюсы: проще для SEO (HTML приходит сразу), не требует JavaScript на клиенте;

    • Минусы: медленнее с точки зрения пользователя, больше нагрузки на сервер.

  2. Клиентский роутинг (SPA — Single Page Application): сервер отдает один HTML-файл, а JavaScript на стороне клиента (например, React Router, Vue Router) управляет URL и подгружает нужные «страницы» (на самом деле, компоненты) динамически, без полной перезагрузки:

    • Плюсы: очень быстрое переключение между «страницами», поведение как у нативного приложения;

    • Минусы: сложнее с SEO (изначально решается с помощью SSR — Server-Side Rendering), первоначальная загрузка может быть дольше.

Паттерны проектирования роутов

  • Статические пути: /about, /contact.

  • Динамические параметры: Используются для идентификации конкретного ресурса:

    • Путь: /users/:userId или /products/:productId;

    • Пример: URL /users/annasmith -> параметр userId = "annasmith".

  • Вложенные роуты (Nested Routes): Отражают иерархию в UI:

    • Роут: /settings/:tab (e.g., /settings/profile, /settings/security);

    • Роут: /dashboard/analytics/overview.


Query Parameters (Параметры строки запроса)

Это часть URL, которая начинается с ? и состоит из пар ключ=значение, разделенных &.
https://example.com/products?category=watches&sort=price\_asc&page=2

Когда использовать?

  • Фильтрация, сортировка, поиск:

    • ?category=electronics&price_min=100&price_max=500;

    • ?sort=date_desc (сортировка по дате, по убыванию);

    • ?q=search+query (поисковый запрос).

  • Пагинация: ?page=3&limit=25.

  • Настройки представления: ?view=grid или ?view=list.

  • Отслеживание (UTM-метки): ?utm_source=newsletter&utm_medium=email&utm_campaign=promo.

  • Сохранение состояния, которое не должно быть в пути: Параметры запроса не уникальны для страницы. Страница /products с разными query-параметрами — это все та же страница /products, просто в разном состоянии.

Важно: Query-параметры не влияют на то, какой HTML-документ вернет сервер (в классической модели). Они обрабатываются уже на загруженной странице.


Hash (Фрагмент)

Это часть URL, которая начинается с символа #.
https://example.com/documentation#chapter-2

Когда использовать?

  1. Якорь на странице (Основное назначение): Браузер автоматически прокручивает страницу к элементу с id="chapter-2". Это чисто клиентская навигация, серверу хэш не отправляется.

  2. Клиентский роутинг в старых SPA (History API): Раньше, до появления History API, хэш (# и #!) использовали для организации роутинга в одностраничных приложениях, так как изменение хэша не вызывает перезагрузки страницы и не отправляется на сервер:

    • Пример: example.com/#/dashboard, example.com/#/users;

    • Сейчас это устаревший подход. Современные фреймворки используют History Mode (роуты без #), который создает красивые URL вида example.com/dashboard. Это требует правильной настройки сервера.


Сводная таблица: что и когда использовать?

Компонент URL

Пример

Когда использовать?

Отправляется на сервер?

Путь (Path)

/catalog/watches/racer-x

Для определения ресурса или страницы. Основа SEO и структуры сайта.

Да

Query-параметры

?color=blue&size=42

Для состояния страницы: сортировка, фильтры, поиск, пагинация. Не уникально для страницы.

Да

Хэш (Fragment)

#specifications

Для внутренней навигации по разделам одной страницы. (Устарел для роутинга между страницами в SPA).

Нет


Как получить части URL в JavaScript?

В браузере весь текущий URL доступен в глобальных объектах window.location или просто location. Этот объект содержит все части URL в разобранном виде.

Пример URL для разбора:
https://www.example.com:8080/catalog/search?q=watch&sort=price#product-list

// Объект location для нашего примера console.log(location); // Выведет: // { //   href: "https://www.example.com:8080/catalog/search?q=watch&sort=price#product-list", //   protocol: "https:", //   hostname: "www.example.com", //   port: "8080", //   host: "www.example.com:8080", // hostname + port //   pathname: "/catalog/search", //   search: "?q=watch&sort=price", //   hash: "#product-list" // }  // 1. Получить весь URL const fullUrl = location.href;  // 2. Получить путь (Path) const path = location.pathname; // Вернёт: "/catalog/search"  // 3. Получить строку query-параметров const searchString = location.search; // Вернёт: "?q=watch&sort=price"  // 4. Получить хэш const hash = location.hash; // Вернёт: "#product-list"  // 5. Работа с Query-параметрами // Простой способ получить значение конкретного параметра const urlParams = new URLSearchParams(location.search); const searchQuery = urlParams.get('q'); // Вернёт: "watch" const sortType = urlParams.get('sort'); // Вернёт: "price" const nonExistentParam = urlParams.get('page'); // Вернёт: null  // Перебрать все параметры for (let [key, value] of urlParams) {   console.log(`${key}: ${value}`); } // Выведет: // q: watch // sort: price  // 6. Динамическое создание URL с параметрами // Полезно для формирования ссылок с query-параметрами const newParams = new URLSearchParams(); newParams.append('category', 'electronics'); newParams.append('page', '2'); const newUrl = `${location.origin}/search?${newParams.toString()}`; // newUrl будет: "https://www.example.com:8080/search?category=electronics&page=2" 

Работа с «неидеальными» Query-параметрами

На практике вы часто будете сталкиваться с query-строками, которые не соответствуют идеальному шаблону ключ=значение. JavaScript-методы должны корректно обрабатывать эти случаи.

Рассмотрим пример URL с проблемными параметрами:
https://example.com/search?q=watch&sort=&page=2&filter=size&filter=color&flag&&invalid\_value=#test

Разберем его части:

  • q=watch — нормальный параметр;

  • sort= — ключ есть, значение пустое;

  • page=2 — нормальный параметр;

  • filter=size&filter=color — два параметра с одинаковым ключом;

  • flag — ключ без знака равенства и значения;

  • &invalid_value= — значение без ключа (редко, но бывает);

  • #test — хэш.

Как это обрабатывает URLSearchParams?

// Представим, что мы на странице с таким URL const urlParams = new URLSearchParams('?q=watch&sort=&page=2&filter=size&filter=color&flag&&invalid_value=');  // 1. Нормальный параметр console.log(urlParams.get('q')); // "watch"  // 2. Параметр с пустым значением console.log(urlParams.get('sort')); // "" (пустая строка, не null!)  // 3. Несколько параметров с одинаковым ключом // .get() возвращает только ПЕРВОЕ значение console.log(urlParams.get('filter')); // "size"  // Чтобы получить все значения, нужно использовать .getAll() console.log(urlParams.getAll('filter')); // ["size", "color"]  // 4. Ключ без значения и без знака "=" (flag) // Интерпретируется как параметр с пустым значением console.log(urlParams.get('flag')); // "" (пустая строка)  // 5. Значение без ключа (invalid_value=) // Интерпретируется как параметр с ключом "invalid_value" и пустым значением console.log(urlParams.get('invalid_value')); // ""  // 6. Проверка существования ключа // .has() проверяет наличие ключа, даже если значение пустое console.log(urlParams.has('sort')); // true console.log(urlParams.has('flag')); // true console.log(urlParams.has('nonexistent')); // false  // 7. Перебор всех параметров for (let [key, value] of urlParams) {   console.log(`Ключ: "${key}", Значение: "${value}"`); } // Выведет: // Ключ: "q", Значение: "watch" // Ключ: "sort", Значение: "" // Ключ: "page", Значение: "2" // Ключ: "filter", Значение: "size" // Ключ: "filter", Значение: "color" // Ключ: "flag", Значение: "" // Ключ: "invalid_value", Значение: "" 

Важные выводы

  1. Пустое значение (?key=) ≠ Отсутствие значения (?key) ≠ Отсутствие ключа:

    • И ?key=, и ?key вернут "" (пустую строку) при вызове .get('key');

    • Но в URL они выглядят по-разному, и некоторые бэкенд-фреймворки могут интерпретировать их неодинаково.

  2. Метод .get() всегда возвращает строку или null. Если значения нет — вернется null. Если значение пустое — вернется пустая строка "".

  3. Для работы с множественными значениями используйте .getAll(). Это особенно важно для чекбоксов, мультиселектов в фильтрах.

Практический совет: всегда явно проверяйте и нормализуйте параметры.

// Вместо этого: if (urlParams.get('sort')) {    // Это не сработает, если sort="", а нам нужно было обработать и этот случай }  // Делайте так: const sortValue = urlParams.get('sort'); if (sortValue !== null) {   // Обрабатываем, даже если sortValue является пустой строкой   // Это означает, что параметр 'sort' был в URL (пусть и без значения) }  // Или так: if (urlParams.has('sort')) {   // Параметр 'sort' присутствует в URL (даже без значения) } 

Этот подход делает ваш код более надежным и защищенным от нестандартных, но возможных входных данных.


Чеклист правильного проектирования URL

  1. URL читается как путь: /blog/category/article-name;

  2. Используется нижний регистр и дефисы;

  3. Динамические ID скрыты там, где важен смысл: /users/annasmith, а не /users/4815162342;

  4. Query-параметры используются для необязательных настроек (сортировка, фильтры), а не для обязательной информации о странице;

  5. Структура предсказуема: пользователь, посмотрев на URL, может понять, где он и как перейти на главную страницы раздела;

  6. Для SPA настроен History Mode (убирает # из URL), а сервер сконфигурирован корректно (отдавать index.html на все несуществующие пути, которые обрабатывает фронтенд).

Потратив время на проектирование правильной URL-структуры, вы создаете не только удобный и понятный сайт для пользователей, но и закладываете основу для его легкой поддержки и успешного продвижения в поисковых системах.

Также к прочтению:

20 частых антипаттернов в React и как их исправить: кратко, понятно, без мифов


ссылка на оригинал статьи https://habr.com/ru/articles/943918/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *