Вы создали красивое React-приложение с клиентской маршрутизацией, но при обновлении страницы или прямом переходе по URL получаете предательскую ошибку 404? Это классическая проблема SPA, которую решает правильная конфигурация веб-сервера. В этой статье мы разберем, как настроить Nginx для безупречной работы с React Router, чтобы ваше приложение вело себя как полноценное веб-приложение, а не как набор статических файлов.
Почему React Router и Nginx конфликтуют?
React Router — это клиентский маршрутизатор. Он управляет переходами между «страницами» вашего приложения, не запрашивая новый HTML с сервера. Когда пользователь заходит на /about прямо из адресной строки, Nginx ищет папку или файл about в корневой директории, не находит его и возвращает 404. Серверу нужно «объяснить», что все запросы (кроме статических файлов) должны перенаправляться на index.html.
Важно: Эта настройка актуальна для React Router v4+, Vue Router, и других SPA-фреймворков с HTML5 History API (не HashRouter).
Базовая конфигурация Nginx
Создайте или отредактируйте конфигурационный файл для вашего сайта (обычно в /etc/nginx/sites-available/). Вот минимальная рабочая конфигурация:
server {
listen 80;
server_name 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)$ {
expires 1y;
add_header Cache-Control \"public, immutable\";
}
}
Разбираем ключевую директиву
Волшебство происходит в блоке location /:
try_files $uri $uri/ /index.html— директива пытается найти запрошенный файл ($uri) или папку ($uri/). Если ничего не найдено, сервер отдаетindex.html.- React Router, загрузившись из
index.html, «увидит» URL/aboutв адресной строке и отрендерит соответствующий компонент.
Продвинутая настройка для продакшена
Для реального проекта нужно добавить сжатие, безопасные заголовки и обработку ошибок:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/your-app/build;
index index.html;
# Gzip сжатие
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;
location / {
try_files $uri $uri/ /index.html;
# Безопасные заголовки
add_header X-Frame-Options \"SAMEORIGIN\";
add_header X-Content-Type-Options \"nosniff\";
}
# Статические файлы с долгим кэшированием
location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff2?|ttf|eot)$ {
expires 1y;
add_header Cache-Control \"public, immutable\";
access_log off;
}
# Кастомная страница 404 (опционально)
error_page 404 /index.html;
}
Совет: Всегда проверяйте конфигурацию командой sudo nginx -t перед перезагрузкой сервера (sudo systemctl reload nginx).
Особые случаи: API-прокси и поддиректории
Проксирование API-запросов
Если ваш фронтенд общается с бэкендом на другом порту/домене:
location /api/ {
proxy_pass http://localhost:3000; # Ваш бэкенд
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
Размещение приложения в поддиректории
Если React-приложение должно работать по пути /app:
- Настройте
basenameв React Router:<BrowserRouter basename=\"/app\"> - В Nginx укажите:
root /var/www/your-app/build;иtry_files $uri $uri/ /app/index.html;
FAQ: Часто задаваемые вопросы
Почему после настройки стили/скрипты не грузятся?
Проверьте базовый путь (publicPath) в сборке. В Create React App можно задать \"homepage\" в package.json или использовать переменную окружения PUBLIC_URL.
Как настроить для HashRouter (#)?
Для HashRouter настройка Nginx не требуется — всё после # не отправляется на сервер. Но этот режим устарел и имеет недостатки для SEO.
Нужно ли настраивать что-то для Docker-развертывания?
Конфигурация идентична. Просто скопируйте nginx.conf в контейнер и укажите его как точку входа. Используйте официальный образ Nginx на Alpine для минимального размера.
Как проверить, что настройка работает?
1. Разместите build-папку в корневой директории Nginx. 2. Перейдите на корневой URL — должно открыться приложение. 3. Вручную введите путь, например, /dashboard — не должно быть 404. 4. Обновите страницу на любом маршруте — приложение должно остаться.