JavaScript превратил статические документы в программное обеспечение. Одностраничные приложения (SPA) работают мгновенно. Никаких полных перезагрузок страниц, никаких мерцающих белых экранов. Но за эту скорость приходится платить, и многие команды игнорируют эту цену: базовые механизмы веба начинают разрушаться. Навигация становится хрупкой. Поисковым системам трудно следовать по путям. Скринридеры теряются. А пользователи оказываются заперты в интерфейсах, которые выглядят как веб-сайты, но ведут себя как сломанные десктопные приложения.
Виновником обычно является div с обработчиком onClick.
Перестаньте использовать div в качестве ссылок
div не несет семантического смысла. Это просто коробка. Когда вы прикрепляете к нему обработчик клика и используете его для перенаправления пользователя на новый вид, вы просите браузер относиться к картонной коробке как к двери. Браузер отказывается. Как и любой инструмент, построенный вокруг браузера.
Скринридеры не объявляют div как ссылку или кнопку. Они пропускают его или читают как обычный текст. Пользователь, использующий голосовое управление, не сможет на него нацелиться. Поисковый робот, сканирующий вашу страницу на наличие доступных URL-адресов, не увидит ничего, по чему можно перейти. Вашего маршрута как будто и не существует.
Хуже того, вы теряете привычное поведение. Настоящая ссылка позволяет щелкнуть правой кнопкой мыши, чтобы открыть её в новой вкладке, добавить место назначения в закладки или скопировать адрес для отправки. Пользователи, использующие клавиатуру, ожидают, что они смогут дойти до ссылки клавишей Tab и открыть её клавишей Enter. div не предлагает ничего из этого. Даже если вы прикрутите tabIndex, role="link" и слушатели клавиатуры, вы будете плохо воссоздавать то, что браузер дает вам бесплатно. И вы обязательно забудете о каком-нибудь пограничном случае. Вы всегда их забываете.
Используйте якоря для переходов, кнопки — для действий
HTML уже решил эту проблему. Путаница возникает из-за того, что оба элемента выглядят кликабельными, поэтому разработчики относятся к ним как к взаимозаменяемым. Но это не так.
Используйте тег <a>, когда хотите переместить пользователя на новый URL. Не имитацию смены представления или изменение состояния, а на реальный адрес. Атрибут href должен содержать настоящий адрес:
<a href="/docs">Documentation</a>
Вот и всё. Если пользователь куда-то направляется, используйте ссылку.
Используйте <button>, когда на текущей странице что-то происходит. Кнопки предназначены для таких действий, как:
- Открытие модального окна
- Отправка формы
- Сохранение настроек
- Переключение меню
Ссылки — для переходов. Кнопки — для действий. Смешивание этих двух типов путает интерфейс и нарушает ожидания пользователей.
Позвольте браузеру делать свою работу
Современные браузеры — это результат десятилетий эволюции и стандартизации. Они справляются с безопасностью, историей, префетчингом (предварительной загрузкой) и доступностью лучше, чем любой ваш самописный JavaScript.
Настоящий тег якоря автоматически пополняет стек истории браузера. Он работает с нативным контекстным меню. Он участвует во встроенных алгоритмах префетчинга браузера, когда пользователь наводит на него курсор или фокусируется на нем, что делает ваше приложение быстрее без написания единой строчки кода. Он учитывает предпочтения пользователя по открытию ссылок. Он взаимодействует с менеджерами паролей, инструментами перевода и режимами чтения.
Когда вы заменяете это функцией навигации на JavaScript, вы отказываетесь от всего этого. Вы не просто теряете возможности; вы заставляете пользователей отказываться от привычек, которые они выработали на всех остальных сайтах в интернете. Это не техническое решение. Это враждебный пользовательский опыт.
Проверяйте, что на самом деле рендерит ваш фреймворк
React Router, Vue Router, компоненты Link в Next.js, SvelteKit. Эти инструменты делают клиентскую маршрутизацию легкой. Но абстракция порождает ошибки.
Инспектируйте свой DOM. Откройте инструменты разработчика в браузере и посмотрите на элементы, которые генерирует ваш фреймворк. Компонент <Link> должен рендериться как настоящий тег <a> с валидным атрибутом href в итоговом HTML. Если он рендерится как span, div или что-то другое без надлежащего href, значит, ваша абстракция подвела вас. Исправьте компонент. Переопределите стандартное поведение. Используйте проп passHref или его эквивалент во фреймворке. Не доверяйте фреймворку на слово — всегда проверяйте результат.
Это также важно для предотвращения ошибок гидратации (hydration mismatches). Если сервер рендерит ссылку, а клиент при гидратации превращает её в не-ссылку, вы создаете ошибки доступности, которые трудно отследить, потому что в исходном коде HTML выглядит правильно, а в живом DOM — нет.
Запретите фейковые переходы
Существует паттерн, который никак не умрет: href="javascript:void(0)". Разработчики используют его, когда хотят создать нечто, что выглядит как ссылка, но ведет себя как кнопка — обычно потому, что не хотят стилизовать кнопку или потому, что этого требует старая кодовая база.
Стоп. Это не URL. Это не указывает браузеру адрес назначения. Это засоряет стек истории неиспользуемыми состояниями. Это нарушает историю браузера и доступность. Это ловушка. Если вам нужно поведение клика без навигации, вам нужен <button>. Стилизуйте его так, как вам угодно. CSS все равно, является ли элемент кнопкой или ссылкой. А вашим пользователям — нет.
Пишите текст, объясняющий, куда перейдет пользователь
Слова внутри вашей ссылки имеют значение. Пользователи программ экранного доступа часто вызывают список всех ссылок на странице для быстрого сканирования. Если все ваши ссылки называются «Читать далее» или «Нажмите здесь», этот список превращается в бесполезный шум.
Будьте конкретны. Сравните эти варианты:
- Плохо:
<a href="/security/api-guide">Read more</a> - Хорошо:
<a href="/security/api-guide">Read the API security guide</a>
Второй вариант точно говорит пользователю, что он найдет. Он дает поисковым системам контекст о целевой странице. Он делает список ссылок удобным для навигации. Описательный текст ссылок — это одна из самых простых и дешевых побед в обеспечении доступности, которую вы можете одержать.
Тестируйте всерьез
Архитектура ничего не значит, если вы ее не проверяете.
Во-первых, протестируйте навигацию с клавиатуры. Отключите мышь. Пройдитесь клавишей Tab по каждому интерактивному элементу на вашем сайте. Каждая настоящая ссылка должна иметь видимую рамку фокуса — не едва заметное свечение, которое теряется на фоне, а четкое кольцо, которое заметит даже уставший глаз. Нажмите Enter. Ссылка должна активироваться. Если Tab пропускает элемент или Enter ничего не делает — у вас баг.
Во-вторых, протестируйте маршруты на уровне сервера. Клиентский роутинг — это лишь тонкая оболочка. Если пользователь добавит /dashboard/reports в закладки и вернется завтра или нажмет «обновить», ваш сервер должен знать, как отдать эту страницу. Настройте ваш обратный прокси-сервер или серверный фреймворк так, чтобы при неизвестных путях происходил переход к оболочке приложения или напрямую отдавался правильный HTML. Ошибка 404 при обновлении страницы — это не мелкий баг. Это нарушенное обещание.
JavaScript — это мощный слой
