Магия Nginx и React Router: Как заставить SPA работать правильно

Магия Nginx и React Router: Как заставить SPA работать правильно

Вы создали красивое React-приложение с клиентской маршрутизацией через React Router, собрали его, загрузили на сервер, а при обновлении страницы или прямом переходе по URL получаете предательскую ошибку 404? Это классическая проблема одностраничных приложений (SPA), и её решение лежит в правильной настройке веб-сервера. В этой статье мы подробно разберём, как настроить Nginx для безупречной работы с React Router, чтобы ваше приложение вело себя как полноценное веб-приложение, а не как набор статичных файлов.

Почему React Router и Nginx конфликтуют?

React Router работает на клиенте. Он манипулирует историей браузера и отрисовывает компоненты динамически, без запросов к серверу при каждом переходе. Когда пользователь заходит на корневой URL (например, yourdomain.com), Nginx отдаёт index.html, и React Router берёт управление на себя. Но если пользователь обновит страницу на yourdomain.com/about, Nginx попытается найти на диске файл /about.html или /about/index.html. Такого файла нет — отсюда и ошибка 404.

Суть решения: нужно научить Nginx при любом запросе (кроме запросов к реальным статическим файлам, вроде CSS, JS, изображений) отдавать тот самый index.html. Тогда React Router, загрузившись, сможет проанализировать URL в адресной строке и отрисовать нужный роут.

Базовая конфигурация Nginx для React Router

Вот минимальный рабочий конфигурационный файл для сервера Nginx, который решает проблему. Обычно он находится в /etc/nginx/sites-available/your-site.

Основной блок server

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    root /var/www/your-react-app/build;
    index index.html;

    # Отдача статических файлов
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Кэширование статических ассетов
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        try_files $uri =404;
    }

    # Обработка ошибок (опционально, но рекомендуется)
    error_page 404 /index.html;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}

Директива try_files $uri $uri/ /index.html; — это сердце настройки. Она говорит Nginx: "Попробуй найти запрошенный файл ($uri). Если не найдёшь, попробуй найти директорию с таким именем ($uri/). Если и это не сработает — отдай файл /index.html". Таким образом, все запросы к несуществующим путям (которые на самом деле являются клиентскими роутами) перенаправляются на главный HTML-файл приложения.

Продвинутые сценарии и оптимизация

Работа с вложенными роутами и API-прокси

Если ваше приложение использует API-бэкенд на другом сервере или порту, нужно добавить проксирование.

location /api/ {
    proxy_pass http://localhost:3001; # Адрес вашего API-сервера
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Важно! Расположение блока location /api/ в конфиге имеет значение. Более специфичные location-директивы должны быть объявлены до общей директивы location /, иначе они не будут обработаны корректно.

Настройка для React Router с Basename

Если вы используете <BrowserRouter basename="/app">, то все ваши роуты будут начинаться с /app. Nginx нужно об этом сообщить, изменив root и директиву try_files.

root /var/www/your-react-app/build;
location /app/ {
    # Указываем, что index.html лежит относительно корня, заданного выше
    try_files $uri $uri/ /app/index.html;
}

Включение gzip-сжатия

Для ускорения загрузки JavaScript-бандлов React-приложения обязательно включите сжатие.

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;

Проверка и перезагрузка конфигурации

  1. Проверьте синтаксис конфигурационного файла: sudo nginx -t
  2. Если тест прошёл успешно, перезагрузите Nginx: sudo systemctl reload nginx или sudo nginx -s reload
  3. Очистите кэш браузера и проверьте работу приложения, переходя по прямым ссылкам и обновляя страницы.

FAQ: Часто задаваемые вопросы

Почему я получаю 404 при обновлении страницы в React-приложении?

Потому что Nginx по умолчанию ищет на диске файл, соответствующий пути в URL. Для SPA такого файла не существует. Решение — настроить try_files для отдачи index.html при любом запросе к нестатическому файлу.

Нужно ли как-то особо настраивать сам React Router?

Нет, React Router на клиенте работает как обычно. Вся магия происходит на стороне сервера (Nginx). Убедитесь только, что в production-сборке ваши относительные пути к статике корректны (часто для этого используют настройку homepage в package.json или переменную PUBLIC_URL).

Будет ли это работать с HashRouter (#)?

Да, с HashRouter эта проблема изначально не возникает, так как часть URL после # не отправляется на сервер. Но HashRouter считается устаревшим подходом для большинства проектов.

Как настроить Nginx для работы с React Router в поддиректории (например, domain.com/my-app)?

Используйте директиву alias или измените root и настройте try_files с учётом префикса пути, как показано в разделе про basename.

Можно ли так же настроить Apache или другие серверы?

Да, принцип тот же. Для Apache используется файл .htaccess с правилом RewriteRule, которое перенаправляет все запросы на index.html.