Инлайн-динамические импорты

Параметр server.proxy используется для настройки проксирования HTTP-запросов во время работы dev-сервера Vite. Механизм позволяет перенаправлять запросы с frontend-приложения на backend-сервер, скрывать реальные адреса API, обходить ограничения CORS и организовывать локальную разработку без изменения клиентского кода.

Без прокси frontend обычно обращается к API напрямую:

fetch('http://localhost:3000/api/users')

Такой подход создаёт ряд проблем:

  • жёсткая привязка к адресу backend;
  • ошибки CORS;
  • необходимость менять URL между development и production;
  • утечка внутренних адресов API;
  • сложность переключения между окружениями.

server.proxy решает эти задачи за счёт промежуточного проксирующего слоя внутри dev-сервера Vite.


Базовая настройка прокси

Простейшая конфигурация выглядит следующим образом:

import { defineConfig } from 'vite'

export default defineConfig({
    server: {
        proxy: {
            '/api': {
                target: 'http://localhost:3000'
            }
        }
    }
})

Теперь запрос:

fetch('/api/users')

будет автоматически перенаправлен на:

http://localhost:3000/api/users

Frontend при этом продолжает работать через Vite, например:

http://localhost:5173

Как работает проксирование

Последовательность обработки:

  1. Браузер отправляет запрос на Vite dev server.
  2. Vite анализирует URL.
  3. Если URL совпадает с ключом прокси (/api), запрос перенаправляется.
  4. Backend получает запрос как обычный HTTP-запрос.
  5. Ответ возвращается через Vite обратно в браузер.

Схема:

Browser
   ↓
Vite Dev Server
   ↓
Backend API

Прокси для REST API

Наиболее распространённый сценарий:

proxy: {
    '/api': {
        target: 'http://localhost:8080'
    }
}

Frontend:

axios.get('/api/posts')

Backend получает:

GET http://localhost:8080/api/posts

Использование changeOrigin

Некоторые backend-серверы проверяют заголовок Host. Для корректной работы используется changeOrigin.

proxy: {
    '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
    }
}

Что делает changeOrigin

Без него:

Host: localhost:5173

С ним:

Host: localhost:8080

Это особенно важно при работе с:

  • nginx;
  • Apache;
  • GraphQL;
  • облачными API;
  • системами авторизации;
  • reverse proxy.

Перезапись путей через rewrite

Иногда backend не ожидает префикс /api.

Например:

Frontend:

/api/users

Backend ожидает:

/users

Используется rewrite:

proxy: {
    '/api': {
        target: 'http://localhost:3000',
        rewrite: (path) => path.replace(/^\/api/, '')
    }
}

Теперь:

/api/users

станет:

/users

Пример полной конфигурации

import { defineConfig } from 'vite'

export default defineConfig({
    server: {
        proxy: {
            '/api': {
                target: 'http://localhost:5000',
                changeOrigin: true,
                secure: false,
                rewrite: (path) => path.replace(/^\/api/, '')
            }
        }
    }
})

Параметр secure

Используется при работе с HTTPS.

secure: false

Позволяет игнорировать ошибки SSL-сертификатов.

Особенно полезно:

  • при self-signed сертификатах;
  • локальном HTTPS;
  • тестовых серверах;
  • внутренней инфраструктуре.

Пример:

proxy: {
    '/api': {
        target: 'https://localhost:8443',
        secure: false
    }
}

Работа с несколькими API

Vite поддерживает несколько прокси одновременно.

proxy: {
    '/api': {
        target: 'http://localhost:3000'
    },

    '/auth': {
        target: 'http://localhost:4000'
    },

    '/files': {
        target: 'http://localhost:5000'
    }
}

Маршрутизация:

Путь Сервер
/api/* localhost:3000
/auth/* localhost:4000
/files/* localhost:5000

Прокси для WebSocket

Vite умеет проксировать WebSocket-соединения.

proxy: {
    '/socket': {
        target: 'ws://localhost:8080',
        ws: true
    }
}

Подключение:

const socket = new WebSocket('ws://localhost:5173/socket')

Vite автоматически перенаправит соединение.


WebSocket с HTTPS

proxy: {
    '/socket': {
        target: 'wss://localhost:8443',
        ws: true,
        secure: false
    }
}

Использование configure

Опция configure предоставляет доступ к экземпляру proxy.

proxy: {
    '/api': {
        target: 'http://localhost:3000',

        configure: (proxy, options) => {
            proxy.on('proxyReq', (proxyReq, req) => {
                console.log(req.url)
            })
        }
    }
}

Через неё можно:

  • логировать запросы;
  • модифицировать заголовки;
  • анализировать ошибки;
  • добавлять middleware;
  • реализовывать кастомное поведение.

Добавление заголовков

proxy: {
    '/api': {
        target: 'http://localhost:3000',

        configure: (proxy) => {
            proxy.on('proxyReq', (proxyReq) => {
                proxyReq.setHeader('X-Dev-Proxy', 'vite')
            })
        }
    }
}

Логирование ошибок

proxy: {
    '/api': {
        target: 'http://localhost:3000',

        configure: (proxy) => {
            proxy.on('error', (err) => {
                console.error(err)
            })
        }
    }
}

Динамическое переключение API

Адрес backend можно выбирать через переменные окружения.

VITE_API_URL=http://localhost:3000

Конфигурация:

import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
    const env = loadEnv(mode, process.cwd())

    return {
        server: {
            proxy: {
                '/api': {
                    target: env.VITE_API_URL,
                    changeOrigin: true
                }
            }
        }
    }
})

Разделение development и production

Важно понимать:

server.proxy работает только в dev-режиме.

Во время production-сборки Vite не запускает dev server, поэтому прокси отсутствует.


Как организуют proxy в production

Обычно используются:

  • nginx;
  • Apache;
  • Traefik;
  • Caddy;
  • Node.js reverse proxy.

Пример nginx:

location /api {
    proxy_pass http://backend:3000;
}

Проблемы CORS

Без прокси:

localhost:5173 → localhost:3000

Браузер считает это разными origin.

Возникают ошибки:

Access-Control-Allow-Origin

С прокси:

localhost:5173 → localhost:5173/api

Для браузера запрос остаётся same-origin.


Использование с Axios

import axios from 'axios'

const api = axios.create({
    baseURL: '/api'
})

Запрос:

api.get('/users')

Использование с Fetch

fetch('/api/posts')

Прокси и cookies

При использовании авторизации важно корректно проксировать cookies.

proxy: {
    '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true
    }
}

В большинстве случаев этого достаточно для работы:

  • JWT;
  • session cookies;
  • CSRF;
  • OAuth.

Иногда backend возвращает cookies с неправильным доменом.

Используется:

cookieDomainRewrite: 'localhost'

Пример:

proxy: {
    '/api': {
        target: 'http://localhost:3000',
        cookieDomainRewrite: 'localhost'
    }
}

cookiePathRewrite: '/'

Работа с GraphQL

proxy: {
    '/graphql': {
        target: 'http://localhost:4000',
        changeOrigin: true
    }
}

Запросы:

fetch('/graphql', {
    method: 'POST'
})

Прокси и Docker

Частая конфигурация:

proxy: {
    '/api': {
        target: 'http://backend:3000'
    }
}

где:

backend

— имя Docker-контейнера.


Прокси и HTTPS frontend

Если Vite запускается через HTTPS:

server: {
    https: true
}

прокси продолжает работать аналогично.


Использование RegExp

Вместо строки можно использовать регулярные выражения.

proxy: {
    '^/fallback/.*': {
        target: 'http://localhost:3000'
    }
}

Проксирование всех запросов

Иногда используется глобальный proxy:

proxy: {
    '/': {
        target: 'http://localhost:3000'
    }
}

Но такой подход опасен:

  • ломает HMR;
  • может конфликтовать с ресурсами Vite;
  • усложняет маршрутизацию.

Исключение путей

Более безопасный вариант:

proxy: {
    '/api': {
        target: 'http://localhost:3000'
    }
}

Использование функции вместо объекта

proxy: {
    '/api': 'http://localhost:3000'
}

Это сокращённая форма.

Полный объект предпочтительнее, поскольку поддерживает:

  • rewrite;
  • ws;
  • secure;
  • headers;
  • configure;
  • cookie rewrite.

Отладка прокси

Полезные способы диагностики:

Проверка URL

console.log(import.meta.env)

Логирование запросов

configure: (proxy) => {
    proxy.on('proxyReq', (proxyReq, req) => {
        console.log(req.method, req.url)
    })
}

Проверка backend

curl http://localhost:3000/api/users

Типичные ошибки

Неверный target

target: 'localhost:3000'

Ошибка:

Must provide a proper URL

Правильно:

target: 'http://localhost:3000'

Конфликт HTTPS и HTTP

Frontend:

https://localhost:5173

Backend:

http://localhost:3000

Некоторые браузеры могут блокировать mixed content.


Proxy не работает в production

Частая ошибка — ожидание, что vite build сохранит proxy.

server.proxy относится только к dev server.


Неправильный rewrite

Ошибка:

rewrite: path.replace('/api', '')

Правильно:

rewrite: (path) => path.replace(/^\/api/, '')

Архитектурные рекомендации

Использование единого API-префикса

Рекомендуется:

/api

или:

/backend

Это упрощает:

  • proxy;
  • nginx;
  • Docker;
  • Kubernetes ingress;
  • CDN;
  • микросервисную архитектуру.

Не использовать абсолютные URL в frontend

Плохо:

fetch('http://localhost:3000/api/users')

Хорошо:

fetch('/api/users')

Централизация API

export const API_URL = '/api'

Взаимодействие с HMR

Прокси работает параллельно с системой Hot Module Replacement.

Vite разделяет:

  • внутренние служебные запросы;
  • API-запросы;
  • WebSocket HMR;
  • статические ресурсы.

Поэтому корректно настроенный proxy не мешает обновлению модулей.


Интеграция с backend-framework

Express

target: 'http://localhost:3000'

NestJS

target: 'http://localhost:4000'

Laravel

target: 'http://localhost:8000'

Django

target: 'http://localhost:8000'

Spring Boot

target: 'http://localhost:8080'

Внутреннее устройство

Vite использует библиотеку:

http-proxy

Через неё реализуются:

  • HTTP proxy;
  • HTTPS proxy;
  • WebSocket proxy;
  • headers rewrite;
  • cookies rewrite;
  • request interception.

Полная production-подобная конфигурация

import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
    const env = loadEnv(mode, process.cwd())

    return {
        server: {
            host: true,
            port: 5173,

            proxy: {
                '/api': {
                    target: env.VITE_API_URL,
                    changeOrigin: true,
                    secure: false,

                    rewrite: (path) => {
                        return path.replace(/^\/api/, '')
                    },

                    configure: (proxy) => {
                        proxy.on('proxyReq', (proxyReq, req) => {
                            console.log(
                                '[PROXY]',
                                req.method,
                                req.url
                            )
                        })

                        proxy.on('error', (err) => {
                            console.error(err)
                        })
                    }
                },

                '/socket': {
                    target: env.VITE_WS_URL,
                    ws: true,
                    changeOrigin: true
                }
            }
        }
    }
})