localhost:5173 / vite-proxy

Proxying API requests from the Vite dev server

server.proxy forwards any request whose path starts with a given prefix to another server, so the browser only ever talks to localhost:5173 and no CORS error appears.

The shortest version

Every request starting with /api goes to the backend. The browser still sees a same-origin request, because as far as it knows everything came from 5173.

vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': 'http://localhost:8080',
    },
  },
})

Why this instead of fixing CORS

Because there is nothing to fix. The cross-origin request never happens — the browser talks to 5173, and 5173 talks to the backend server-side, where the same-origin policy does not apply. Loosening CORS on the backend to make local development work is how test settings end up in production.

When the backend does not want the prefix

rewrite strips it. This is the option people usually discover after twenty minutes of 404s from an API that works fine in curl.

vite.config.js
proxy: {
  '/api': {
    target: 'http://localhost:8080',
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/api/, ''),
  },
}

changeOrigin, in one sentence

It rewrites the Host header to the target instead of passing localhost:5173 through. Needed whenever the backend routes by hostname — virtual hosts, most hosted APIs, anything behind a shared ingress.

WebSockets

Socket connections need to be declared, otherwise only the HTTP upgrade request gets proxied and the socket dies immediately.

vite.config.js
proxy: {
  '/socket': {
    target: 'ws://localhost:8080',
    ws: true,
  },
}

Regex keys

A key beginning with ^ is treated as a regular expression instead of a path prefix.

vite.config.js
proxy: {
  '^/(api|auth)/.*': {
    target: 'http://localhost:8080',
    changeOrigin: true,
  },
}

Two things that quietly break it

  • A non-relative base: every proxy key has to carry that base as a prefix, or nothing matches.
  • Proxied requests skip Vite entirely — no transform, no HMR. That is intended, but it means a path you proxy can never also be served by Vite.

Preview has its own proxy

preview.proxy defaults to server.proxy but is a separate option. If the API works on 5173 and 404s on 4173, check that one.

# faq

Questions

Does the proxy remove the path prefix automatically?

No. /api is forwarded as /api unless you add a rewrite.

When do I need changeOrigin?

When the target routes by Host header — virtual hosts and most hosted APIs.

Does the proxy work for WebSockets?

Yes, with ws: true on that entry.

Why does the proxy work in dev but not in the build?

It is a dev server feature. A built site has no proxy — that job belongs to whatever serves it.

# next

Related

  • How the Vite dev server handles requests to and from localhost:5173Three settings cover three different requests: server.proxy forwards a request from 5173 to your backend, server.cors decides which origins may read a response from 5173, and server.allowedHosts decides which hostnames may reach 5173 at all.
  • Rewriting the path when the Vite proxy forwards a requestrewrite is called with req.url — the full path including the query string — and whatever it returns replaces req.url before the request is forwarded, so it has to come back as a path that still starts with a slash.
  • When the Vite proxy does not forward the requestA proxy that appears to do nothing has either never seen the request — because the browser addressed the backend directly, or because a middleware ahead of it answered first — or it saw it and no key matched, and DEBUG=vite:proxy tells the two apart in one line.
  • Proxying WebSocket connections through the Vite dev serverA WebSocket through the dev server needs ws: true (or a target starting with ws:// or wss://) on its proxy entry, because upgrade requests are handled by a separate listener that ignores every rule without one.
  • CORS errors around localhost:5173The Vite dev server only accepts cross-origin requests from localhost, 127.0.0.1 and ::1 by default — anything else has to be allowed explicitly, and setting cors to true opens it to every website.
  • Hot Module Replacement on localhost:5173HMR swaps a changed module in the running page over a WebSocket instead of reloading it; when that WebSocket cannot connect, Vite falls back to full page reloads or stops updating at all.
  • localhost:5173 — the port itselfWhat the address means, common dev ports, and the autocomplete fix.

# sources

Checked against the Vite documentation on 2026-09-11.