The Vite dev server can act as a proxy for your API requests, seamlessly forwarding them to a backend server without you needing to configure a separate proxy or CORS on your actual backend.

Let’s see this in action. Imagine you have a frontend application built with Vite and a separate backend API running on http://localhost:3000. You want your frontend to make requests to /api/users and have Vite forward these requests to http://localhost:3000/api/users.

Here’s a snippet from a vite.config.js file that accomplishes this:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      // string shorthand: /api -> http://localhost:3000/api
      '/api': 'http://localhost:3000',

      // with options
      '/api/users': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});

When your frontend code makes a fetch request to /api/users, Vite intercepts it. Because of the configuration above, it sees the /api prefix and knows to forward the request to http://localhost:3000. The rewrite function in the second example is particularly useful: it tells Vite to remove the /api prefix before sending the request to the target server. So, /api/users becomes /users when it hits http://localhost:3000. This is crucial because your backend API likely doesn’t expect the /api prefix itself.

The primary problem Vite’s proxy solves is simplifying your development workflow. Without it, you’d typically face two main hurdles:

  1. CORS (Cross-Origin Resource Sharing): If your frontend (e.g., running on http://localhost:5173) tries to fetch data from a different origin (your API on http://localhost:3000), the browser will block the request due to CORS policies. You’d have to configure your backend API to send the correct CORS headers, which can be an extra step, especially during rapid development.
  2. Manual Proxy Configuration: You might set up a dedicated proxy server (like Nginx) or use a tool like http-proxy-middleware directly in your build process. This adds complexity to your project setup and configuration.

Vite’s dev server handles both of these automatically. It acts as a transparent intermediary. When a request matches a proxy rule, Vite doesn’t serve it from its own static files or build output. Instead, it forwards the request to the specified target server. The response from the target server is then sent back to your frontend, appearing as if it came from the same origin as your frontend application.

The server.proxy option in vite.config.js is a powerful object where keys are the paths to match (e.g., /api) and values are either a target URL string or an object with more detailed proxy options.

Let’s break down the common options within the proxy configuration object:

  • target: This is the base URL of your backend API. All requests matching the key will be forwarded to this URL.
  • changeOrigin: When set to true, this rewrites the Origin header to match the target’s origin. This is often necessary for backend APIs that rely on the Origin header to determine whether to allow a request. For example, if your frontend is on localhost:5173 and your target is localhost:3000, setting changeOrigin: true makes the request appear to come from localhost:3000 to the backend.
  • rewrite: This is a function that allows you to modify the path of the request before it’s sent to the target. The most common use case, as shown in the example, is to strip a prefix like /api so that http://localhost:3000/api/users receives the request as /users.
  • ws: If you’re using WebSockets, setting ws: true will proxy WebSocket connections.

Consider this common scenario: your backend API is hosted on a separate domain, say https://api.example.com, and you’re developing your frontend locally on http://localhost:5173. You want all requests starting with /api to go to your production API for testing.

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        secure: false, // Important if the target is an HTTPS site with a self-signed certificate or a different domain.
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});

Here, secure: false is often needed when proxying to an HTTPS URL from a local development server, especially if the target’s SSL certificate isn’t trusted by your local Node.js environment (e.g., self-signed certificates). Be cautious with secure: false in production, but it’s a common necessity for local development against external HTTPS APIs.

The most surprising thing about Vite’s proxy is that it doesn’t actually proxy in the traditional sense for static assets. If you have a request that doesn’t match any proxy rules and Vite can’t find a corresponding static file in your public directory or within your build output, it will fall back to serving files from your index.html’s directory (usually index.html itself), enabling client-side routing. This means your proxy rules are only applied to requests that would have otherwise resulted in a 404 or are explicitly targeted by the proxy configuration, ensuring your client-side routing still works seamlessly for non-API requests.

The next logical step after setting up API proxying is often handling environment-specific API endpoints, where you might want different proxy targets depending on whether you’re in a staging or production environment, or even different local API services.

Want structured learning?

Take the full Vite course →