Во время разработки фронтенд-приложение часто запускается отдельно от backend-сервера. Например:
http://localhost:8080;http://localhost:3000.При попытке выполнить запрос:
fetch('/api/users')
браузер обращается к localhost:8080/api/users, а backend
находится на другом порту. Если указать полный адрес:
fetch('http://localhost:3000/api/users')
возникают проблемы:
Механизм devServer.proxy позволяет webpack-dev-server
выступать промежуточным сервером и перенаправлять запросы на
backend.
Схема работы:
Браузер → webpack-dev-server → backend API
Frontend продолжает обращаться к относительным URL:
fetch('/api/users')
Но webpack-dev-server автоматически проксирует запрос на:
http://localhost:3000/api/users
Простейшая конфигурация:
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000'
}
}
}
};
Теперь все запросы, начинающиеся с /api, будут
перенаправляться на backend.
Пример:
/api/users
становится:
http://localhost:3000/api/users
Для работы proxy необходим webpack-dev-server.
Установка:
npm install webpack-dev-server --save-dev
Либо:
yarn add webpack-dev-server --dev
Запуск:
npx webpack serve
или через package.json:
{
"scripts": {
"dev": "webpack serve"
}
}
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
devServer: {
static: {
directory: path.join(__dirname, 'public')
},
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000',
secure: false,
changeOrigin: true
}
}
}
};
Webpack анализирует URL запроса.
Если путь совпадает с ключом proxy:
proxy: {
'/api': {
target: 'http://localhost:3000'
}
}
то запрос перенаправляется.
| Запрос | Результат |
|---|---|
/api/users |
proxy |
/api/posts |
proxy |
/images/logo.png |
обычная раздача |
/main.js |
обычная раздача |
targettarget определяет backend-сервер.
proxy: {
'/api': {
target: 'http://localhost:3000'
}
}
Можно использовать:
target: 'https://api.example.com'
или:
target: 'http://127.0.0.1:5000'
changeOriginНекоторые backend-серверы проверяют заголовок Host.
Если frontend работает на:
localhost:8080
а backend ожидает:
localhost:3000
то запрос может быть отклонён.
Решение:
changeOrigin: true
Пример:
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
Webpack изменит origin запроса автоматически.
secureЕсли backend использует самоподписанный SSL-сертификат:
https://localhost:5000
может возникнуть ошибка TLS.
Для отключения проверки сертификата:
secure: false
Пример:
proxy: {
'/api': {
target: 'https://localhost:5000',
secure: false
}
}
В одном проекте может быть несколько backend-сервисов.
Пример:
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000'
},
'/auth': {
target: 'http://localhost:4000'
},
'/upload': {
target: 'http://localhost:5000'
}
}
}
Маршрутизация:
| URL | Сервер |
|---|---|
/api/users |
3000 |
/auth/login |
4000 |
/upload/file |
5000 |
pathRewriteИногда backend не использует тот же префикс.
Frontend:
/api/users
Backend:
/users
Решение:
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {
'^/api': ''
}
}
}
Теперь:
/api/users
превращается в:
/users
Можно выполнять более сложные преобразования.
Пример:
pathRewrite: {
'^/api/v1': '/v2'
}
Результат:
/api/v1/users
становится:
/v2/users
pathRewrite поддерживает функцию.
pathRewrite: (path, req) => {
return path.replace('/api', '');
}
Пример с логикой:
pathRewrite: (path, req) => {
console.log(req.method);
return path.replace('/api', '');
}
logLevelПозволяет управлять логированием proxy.
proxy: {
'/api': {
target: 'http://localhost:3000',
logLevel: 'debug'
}
}
Возможные значения:
| Значение | Описание |
|---|---|
silent |
без логов |
error |
только ошибки |
warn |
предупреждения |
info |
информационные сообщения |
debug |
максимальная детализация |
Часто используется:
logLevel: 'debug'
Пример:
proxy: {
'/api': {
target: 'http://localhost:3000',
logLevel: 'debug'
}
}
В консоли отображаются:
Webpack умеет проксировать WebSocket-соединения.
Пример:
proxy: {
'/socket': {
target: 'ws://localhost:5000',
ws: true
}
}
Теперь WebSocket:
ws://localhost:8080/socket
будет перенаправлен на:
ws://localhost:5000/socket
Backend часто устанавливает cookie:
Set-Cookie: token=123
Proxy способен корректно передавать cookie между браузером и backend.
Конфигурация:
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
При авторизации возможны ограничения браузера:
SameSite=Lax
Secure
Особенно при HTTPS и разных origin.
Proxy уменьшает количество подобных проблем, потому что frontend работает через единый origin.
Главное преимущество proxy — устранение CORS-проблем.
Без proxy:
localhost:8080 → localhost:3000
Браузер блокирует запрос.
С proxy:
localhost:8080 → localhost:8080/api
Для браузера запрос выглядит локальным.
Без proxy backend часто содержит:
app.use(cors());
или:
res.setHeader('Access-Control-Allow-Origin', '*');
При использовании proxy необходимость в этом во время разработки обычно исчезает.
headersМожно добавлять собственные заголовки.
proxy: {
'/api': {
target: 'http://localhost:3000',
headers: {
'X-Dev-Server': 'webpack'
}
}
}
xfwdДобавляет proxy-заголовки:
X-Forwarded-For
X-Forwarded-Host
X-Forwarded-Proto
Конфигурация:
proxy: {
'/api': {
target: 'http://localhost:3000',
xfwd: true
}
}
timeoutТаймаут backend-запросов:
proxy: {
'/api': {
target: 'http://localhost:3000',
timeout: 5000
}
}
proxyTimeoutОтдельный timeout для proxy-соединения:
proxy: {
'/api': {
target: 'http://localhost:3000',
proxyTimeout: 5000
}
}
При недоступности backend:
ECONNREFUSED
webpack-dev-server выводит ошибку в терминал.
Частые причины:
bypassПозволяет пропускать proxy для определённых запросов.
Пример:
proxy: {
'/api': {
target: 'http://localhost:3000',
bypass: (req) => {
if (req.headers.accept.includes('html')) {
return '/index.html';
}
}
}
}
Можно выбирать target программно.
Пример:
proxy: {
'/api': {
target: 'http://localhost:3000',
router: (req) => {
if (req.headers.host === 'admin.localhost') {
return 'http://localhost:4000';
}
return 'http://localhost:3000';
}
}
}
В крупных системах frontend взаимодействует с множеством сервисов:
/auth
/users
/payments
/notifications
Webpack proxy позволяет организовать единый gateway во время разработки.
Пример:
proxy: {
'/auth': {
target: 'http://localhost:3001'
},
'/users': {
target: 'http://localhost:3002'
},
'/payments': {
target: 'http://localhost:3003'
}
}
Single Page Application часто использует API:
/api/*
и frontend routing:
/profile
/settings
/dashboard
Важно не путать proxy и history fallback.
Правильная настройка:
devServer: {
historyApiFallback: true,
proxy: {
'/api': {
target: 'http://localhost:3000'
}
}
}
Типичная структура:
React → localhost:8080
Express API → localhost:3000
Конфигурация:
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000'
}
}
}
Запрос:
axios.get('/api/users');
Пример:
devServer: {
proxy: {
'/api': {
target: 'http://localhost:5000'
}
}
}
Angular CLI использует похожую схему proxy через отдельный файл:
{
"/api": {
"target": "http://localhost:3000",
"secure": false
}
}
Но принцип полностью совпадает с webpack-dev-server.
Можно задавать массив объектов.
Пример:
proxy: [
{
context: ['/api'],
target: 'http://localhost:3000'
},
{
context: ['/auth'],
target: 'http://localhost:4000'
}
]
Можно использовать шаблоны.
Пример:
proxy: {
'^/api': {
target: 'http://localhost:3000'
}
}
Простейший тест:
fetch('/api/test')
.then(r => r.json())
.then(console.log);
Если backend отвечает корректно — proxy работает.
Ошибка:
ECONNREFUSED
Причина:
target: 'http://localhost:9999'
при отсутствии сервера на порту 9999.
Ошибка:
socket hang up
Причина:
target: 'https://localhost:3000'
при HTTP backend.
Frontend:
/api/users
Backend получает:
/api/users
хотя ожидает:
/users
changeOriginНекоторые backend-framework отклоняют запросы с неправильным
Host.
devServer: {
port: 8080,
hot: true,
historyApiFallback: true,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
secure: false,
logLevel: 'debug',
pathRewrite: {
'^/api': ''
}
}
}
}
Backend:
const express = require('express');
const app = express();
app.get('/users', (req, res) => {
res.json([
{ id: 1, name: 'Alex' }
]);
});
app.listen(3000);
Frontend:
fetch('/api/users')
.then(r => r.json())
.then(console.log);
Webpack:
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {
'^/api': ''
}
}
}
devServer.proxy решает сразу несколько задач:
Во многих современных проектах proxy становится обязательной частью локальной среды разработки, особенно при разделении frontend и backend на независимые приложения.