Привет, Хабр! Меня зовут Алексей Фомин, я Technical Lead во Frontend в компании Devs Universe. В своей работе я часто сталкиваюсь с тем, что даже опытные разработчики не всегда задумываются о проектировании URL-структуры приложения, а ведь это критически важный элемент пользовательского опыта, SEO и архитектуры. В этой статье я хочу системно разобрать анатомию 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 с кодом, который должен выполниться для обработки запроса.
Подходы:
-
Серверный роутинг (Traditional): каждый URL ведет на отдельную HTML-страницу, которую генерирует и возвращает сервер. При переходе по ссылке браузер полностью обновляет страницу:
-
Плюсы: проще для SEO (HTML приходит сразу), не требует JavaScript на клиенте;
-
Минусы: медленнее с точки зрения пользователя, больше нагрузки на сервер.
-
-
Клиентский роутинг (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
Когда использовать?
-
Якорь на странице (Основное назначение): Браузер автоматически прокручивает страницу к элементу с
id="chapter-2". Это чисто клиентская навигация, серверу хэш не отправляется. -
Клиентский роутинг в старых SPA (History API): Раньше, до появления History API, хэш (
#и#!) использовали для организации роутинга в одностраничных приложениях, так как изменение хэша не вызывает перезагрузки страницы и не отправляется на сервер:-
Пример:
example.com/#/dashboard,example.com/#/users; -
Сейчас это устаревший подход. Современные фреймворки используют History Mode (роуты без
#), который создает красивые URL видаexample.com/dashboard. Это требует правильной настройки сервера.
-
Сводная таблица: что и когда использовать?
|
Компонент URL |
Пример |
Когда использовать? |
Отправляется на сервер? |
|
Путь (Path) |
|
Для определения ресурса или страницы. Основа SEO и структуры сайта. |
Да |
|
Query-параметры |
|
Для состояния страницы: сортировка, фильтры, поиск, пагинация. Не уникально для страницы. |
Да |
|
Хэш (Fragment) |
|
Для внутренней навигации по разделам одной страницы. (Устарел для роутинга между страницами в 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", Значение: ""
Важные выводы
-
Пустое значение (
?key=) ≠ Отсутствие значения (?key) ≠ Отсутствие ключа:-
И
?key=, и?keyвернут""(пустую строку) при вызове.get('key'); -
Но в URL они выглядят по-разному, и некоторые бэкенд-фреймворки могут интерпретировать их неодинаково.
-
-
Метод
.get()всегда возвращает строку илиnull. Если значения нет — вернетсяnull. Если значение пустое — вернется пустая строка"". -
Для работы с множественными значениями используйте
.getAll(). Это особенно важно для чекбоксов, мультиселектов в фильтрах.
Практический совет: всегда явно проверяйте и нормализуйте параметры.
// Вместо этого: if (urlParams.get('sort')) { // Это не сработает, если sort="", а нам нужно было обработать и этот случай } // Делайте так: const sortValue = urlParams.get('sort'); if (sortValue !== null) { // Обрабатываем, даже если sortValue является пустой строкой // Это означает, что параметр 'sort' был в URL (пусть и без значения) } // Или так: if (urlParams.has('sort')) { // Параметр 'sort' присутствует в URL (даже без значения) }
Этот подход делает ваш код более надежным и защищенным от нестандартных, но возможных входных данных.
Чеклист правильного проектирования URL
-
URL читается как путь:
/blog/category/article-name; -
Используется нижний регистр и дефисы;
-
Динамические ID скрыты там, где важен смысл:
/users/annasmith, а не/users/4815162342; -
Query-параметры используются для необязательных настроек (сортировка, фильтры), а не для обязательной информации о странице;
-
Структура предсказуема: пользователь, посмотрев на URL, может понять, где он и как перейти на главную страницы раздела;
-
Для SPA настроен History Mode (убирает
#из URL), а сервер сконфигурирован корректно (отдаватьindex.htmlна все несуществующие пути, которые обрабатывает фронтенд).
Потратив время на проектирование правильной URL-структуры, вы создаете не только удобный и понятный сайт для пользователей, но и закладываете основу для его легкой поддержки и успешного продвижения в поисковых системах.
Также к прочтению:
20 частых антипаттернов в React и как их исправить: кратко, понятно, без мифов
ссылка на оригинал статьи https://habr.com/ru/articles/943918/
Добавить комментарий