3 августа 2026
Проблема завершающего слеша в статических сайтах
Обнаружение проблемы
История начинается с реальной проблемы, с которой столкнулся пользователь Rspress.
Пользователь разместил сайт по адресу /next/ и использовал относительную ссылку на главной странице:
В корне домена ссылка работала независимо от того, заканчивался URL на / или нет. Она также корректно разрешалась, когда пользователь посещал /next/:
Однако, когда пользователь посещал /next, ссылка разрешалась в:
Сама страница успешно загружалась, так почему же относительная ссылка потеряла сегмент /next?
Эта проблема заставила меня задуматься: какую именно разницу на самом деле создаёт завершающий слеш?
Корневой URL — особый случай
RFC 3986 определяет пустой путь как эквивалентный / для HTTP(S)-URL. Поэтому, даже если вы вводите https://example.com, браузер всё равно отправляет GET /.
Завершающий слеш изменяет разрешение относительного URL
Браузер разрешает относительные ссылки относительно URL текущей страницы. Он рассматривает последний сегмент /next как имя текущего ресурса, тогда как /next/ означает, что текущий ресурс находится внутри каталога:
На уровне клиентского роутера сопоставление URL обычно более либерально, чем поиск файлов на веб-сервере. Например, React Router по умолчанию не различает пути с завершающим / и без него. Поэтому оба приведённых ниже URL соответствуют одному и тому же маршруту с путём /guide:
/guide/guide/
Это либеральное поведение применяется только при сопоставлении маршрутов; оно не означает, что клиент автоматически нормализует URL. React Router не удаляет .html и не рассматривает /index.html как индекс каталога. Доступность /guide.html и /guide/index.html по-прежнему зависит от дополнительной обработки со стороны фреймворка или веб-сервера.
Это объясняет, почему «страница загружается» и «относительные ссылки разрешаются корректно» — это два разных вопроса. Либеральное сопоставление помогает клиенту найти маршрут, но не изменяет URL в адресной строке. Пока в адресной строке находится /next, браузер разрешает относительные пути согласно первому правилу выше.
Это различие возникло ещё на заре Web, когда пути URL обычно сопоставлялись с файловыми системами. Следующие URL могли указывать на два разных статических результата:
Когда URL указывает на каталог, веб-сервер обычно добавляет завершающий слеш перед поиском index.html. Современные платформы для хостинга могут использовать rewrite-правила, чтобы отдавать одну и ту же страницу для нескольких URL, но это не меняет того, как браузеры разрешают относительные ссылки.
Решение проблемы
Ключевой момент — обеспечить соответствие текущего URL в адресной строке предпочтительному URL, генерируемому сайтом.
Rspress route.cleanUrls определяет, должны ли сгенерированные URL включать расширение .html. Однако это влияет только на ссылки, генерируемые Rspress. Это не может помешать пользователям переходить на /guide.html, /guide/ или другой вариант URL через закладки, результаты поиска или внешние сайты.
Поэтому в версии 2.1.0 Rspress вводит route.cleanUrlsRedirect. При запуске клиента сначала определяется фактический маршрут, а затем с помощью history.replaceState URL в адресной строке обновляется до формата, выбранного параметром cleanUrls. Например, при cleanUrls: true:
/guide.html,/guide/и/guide/index.html→/guide/reference/index.html→/reference/
Нормализация выполняется до рендеринга страницы, не перезагружает страницу и сохраняет base сайта, query-параметры, hash и существующее состояние истории. В приведённом в начале примере Rspress изменяет /next на /next/ до рендеринга, поэтому относительные ссылки больше не выходят за пределы /next/.
Начиная с Rspress 2.1.0, и cleanUrls, и cleanUrlsRedirect будут включены по умолчанию. Клиентская нормализация не может полностью заменить серверный редирект 301/308 или самостоятельно решить проблемы SEO, связанные с дублирующимися URL, но она может предотвратить ошибки приложения, вызванные несогласованными форматами URL.
Полные правила преобразования URL и сведения о конфигурации см. в документации API route.cleanUrlsRedirect.
Лучшие практики
URL-адреса больше не обязательно должны напрямую соответствовать файлам на диске. Использование завершающих слешей — это вопрос политики сайта, а не корректности. Браузеры и поисковые системы по-прежнему рассматривают /about и /about/ как два разных URL.
Чтобы предотвратить ошибки приложения и избежать индексации одной и той же страницы по нескольким URL:
- Сервер: Используйте редиректы 301/308, чтобы свести
/aboutи/about/к одному предпочтительному URL. Варианты вроде/about.htmlи/about/index.htmlможно обрабатывать одновременно. Например, Cloudflare Workers Static Assets по умолчанию поддерживаетauto-trailing-slash: обычные HTML-файлы используют/file, индексные файлы каталогов —/folder/, а остальные варианты получают редирект 307 на соответствующий канонический URL. - Клиент: Включите
cleanUrlsи оставьте клиентский редирект в качестве запасного варианта. Если на платформе хостинга невозможно настроить редиректы, клиент может записать предпочтительный URL в самой ранней точке входа на страницу. Однако это выполняется только после загрузки HTML и JavaScript, поэтому такой подход не может полностью заменить серверный редирект.
Следите за согласованностью всех источников URL: внутренние ссылки, канонические URL, карты сайта и расшариваемые URL должны использовать одну и ту же предпочтительную форму. Остальные варианты должны перенаправляться на неё с помощью ответов 301/308.
Финальные проверки:
/fileи/file/не должны оба отдавать один и тот же контент как два независимых URL.- Публичные URL, статический результат сборки, конфигурация Rspress и правила CDN должны придерживаться одной и той же политики использования завершающих слешей.

