Гайды

Почему страница выдаёт 404 после обновления — а по ссылкам внутри сайта всё открывается

Иллюстрация: пневмопочта, где одна труба упирается в тупик, и робот ведёт от неё обходную трубу к центру

Симптом

Ты задеплоил приложение. Главная открывается. Кликаешь «О проекте» — адрес меняется на /about, страница на месте. Всё работает.

Нажимаешь F5 — 404 Not Found. Или отправляешь ссылку /about другу, а у него ошибка. Хотя пять секунд назад эта же страница была на экране.

А локально всё было идеально, и обновлять можно было сколько угодно.

Смотри, что происходит. Внутри приложения переход на /about делает JavaScript, сервер про это не знает. А при обновлении браузер честно идёт на сервер за файлом /about. У сервера такого файла нет — у него есть только index.html и папка со скриптами. Автор популярного обходного решения для GitHub Pages формулирует это одной фразой: сервер возвращает 404, потому что ничего не знает про /foo.

Подробно механику мы разбирали в статье что такое клиентский роутинг. Здесь — только как починить.

Почему локально работало

Это первое, что сбивает с толку. Dev-сервер Vite по умолчанию работает в режиме appType: 'spa': в документации сказано, что он включает SPA-фолбэк — на любой неизвестный путь отдаёт index.html. Команда vite preview делает то же самое.

То есть локально за тебя уже всё настроено. А обычный хостинг ведёт себя как обычный файловый сервер. Это частный случай общей истории, почему приложение работает локально, но падает на проде.

Причина 1. Хостингу не сказали отдавать index.html на все адреса

Самая частая причина — почти всегда именно она.

Как проверить. Открой внутренний адрес напрямую в режиме инкогнито. Или в терминале:

curl -I https://твой-сайт.app/about

Если в первой строке ответа 404, а в папке сборки (dist) нет файла about/index.html — это она.

Как починить. Нужно правило «переписать любой путь на /index.html», при этом адрес в браузере не меняется. У каждого хостинга своя запись.

  • Netlify. Файл _redirects в папке, которая публикуется (для Vite — положи его в public/):

    /*    /index.html   200
    

    Код 200 превращает перенаправление в подмену: адрес остаётся прежним. По документации Netlify, существующие файлы это правило не перекрывает — скрипты и картинки по-прежнему отдаются как есть.

  • Vercel. Файл vercel.json в корне проекта, пример прямо из их документации для Vite:

    {
      "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
    }
    
  • Cloudflare Pages. Ничего писать не нужно. Если в корне сборки нет файла 404.html, Pages считает проект SPA и сам отдаёт приложение на любой путь. Ловушка обратная: добавил свой 404.html — и режим SPA выключился.

  • Свой сервер на nginx.

    location / {
      try_files $uri $uri/ /index.html;
    }
    

    Nginx ищет файлы по порядку. Если ни один не нашёлся, по документации делается внутреннее перенаправление на последний параметр — наш index.html.

Если выбираешь, куда вообще выкладывать, сравнение Vercel или Netlify поможет.

Причина 2. Хостинг вообще не умеет подменять адреса

Главный пример — GitHub Pages. Правил подмены у него нет, файлы отдаются как лежат.

Как проверить. Сайт на username.github.io, и никакие файлы настроек из списка выше не помогают.

Как починить. Три варианта, от простого к правильному:

  1. Хеш-роутинг. Адреса становятся вида /#/about. Всё после # на сервер не отправляется, поэтому обновление не ломается. Минус — некрасивые адреса. В React Router для этого есть отдельный режим.
  2. Трюк с 404.html. Проект spa-github-pages кладёт особый 404.html, скрипт в котором превращает путь в параметр и перекидывает на главную, а приложение восстанавливает адрес. Работает, но это заплатка.
  3. Переехать на хостинг с подменой — Netlify, Vercel или Cloudflare Pages бесплатно закрывают эту задачу одной строкой.

Причина 3. Сайт лежит в подпапке

Симптом похожий, но хитрее. На username.github.io/my-app/ главная открывается, а на вложенных адресах — 404 или белый экран, и в консоли не грузятся скрипты.

Как проверить. Посмотри, в какой путь смотрят ссылки на скрипты в index.html. Если на /assets/..., а сайт живёт в /my-app/ — нашёл.

Как починить. Сказать сборщику и роутеру, где корень. В Vite, по их документации, для адреса https://<USERNAME>.github.io/<REPO>/ ставят base: '/<REPO>/' в vite.config.js. Роутеру нужен тот же префикс — в React Router это параметр basename.

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

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

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

Правило «всё на index.html» не сломает настоящие 404?

Сломает, если ничего не сделать: сервер начнёт отвечать кодом 200 на любой несуществующий адрес. Поэтому в роутере нужен свой маршрут «страница не найдена» на все неизвестные пути. Чем отличаются коды ответа, разбирали в статье что такое коды HTTP.

Как понять, чья это 404 — хостинга или моего приложения?

Посмотри на саму страницу ошибки. Если это стандартная заглушка хостинга без твоего дизайна — запрос до приложения не дошёл, чини настройки из причин 1–3. Если ошибка оформлена в стиле твоего сайта — подмена уже работает, а в роутере просто нет такого маршрута. Проверь написание пути и список маршрутов.

Почему после исправления всё ещё 404?

Проверь, что файл настроек попал в сборку: _redirects должен оказаться внутри dist, а не остаться в корне репозитория. И что ты задеплоил новую версию, а смотришь не закешированную старую.

Робот KODiQ

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

Все статьи →