Vite apps can be served from a subdirectory, but only if you tell Vite where that subdirectory is.

Let’s see Vite serving an app from /app/ on a web server.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="icon" href="/app/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vite App</title>
    <script type="module" src="/app/src/main.js"></script>
</head>
<body>
    <div id="app"></div>
</body>
</html>

Notice how favicon.ico and main.js are prefixed with /app/. Without this, Vite would assume they are at the root of the domain, leading to 404s when the server tries to load /favicon.ico or /src/main.js instead of /app/favicon.ico or /app/src/main.js.

This is controlled by the base option in your vite.config.js.

Here’s a minimal vite.config.js that sets the base URL:

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

export default defineConfig({
  plugins: [react()],
  base: '/app/', // This is the key!
});

When you run vite build, Vite will process your assets and code, injecting this /app/ prefix into all generated URLs.

For example, your index.html might be transformed to include the correct paths:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="icon" href="/app/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vite App</title>
    <script type="module" src="/app/src/main.js"></script>
</head>
<body>
    <div id="app"></div>
    <script type="module" src="/app/assets/index-a1b2c3d4.js"></script>
</body>
</html>

The base option can also be an empty string '' if your application is hosted at the root of the domain (e.g., https://example.com/). If you don’t specify base at all, Vite defaults to ''.

You can dynamically set the base path at build time using environment variables. This is incredibly useful for CI/CD pipelines where the deployment path might change.

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

export default defineConfig({
  plugins: [react()],
  base: process.env.NODE_ENV === 'production' ? '/my-app-on-server/' : '/',
});

In this example, if the NODE_ENV is production, the base will be /my-app-on-server/. Otherwise (e.g., during development), it will default to /. To use this, you would set the environment variable before running the build command:

NODE_ENV=production vite build

Or, if using a tool like dotenv:

npm install --save-dev dotenv
// vite.config.js
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';

const env = loadEnv(process.argv[process.argv.indexOf('--mode') + 1] || process.env.NODE_ENV, process.cwd(), '');

export default defineConfig({
  plugins: [react()],
  base: env.VITE_APP_BASE_URL || '/',
});

And in your .env file:

VITE_APP_BASE_URL=/my-subdirectory/

Then build like this:

VITE_APP_BASE_URL=/my-subdirectory/ vite build

Or simply rely on the .env file:

vite build --mode production

The base option affects how Vite resolves and rewrites URLs for assets, chunking, and public path. It’s not just for the index.html; it’s a global setting for your entire build output when it comes to asset referencing. For instance, if you have an image public/logo.png and base: '/app/', Vite will ensure its output path is /app/assets/logo-xxxxxx.png and all references point there.

When deploying to platforms like Netlify or Vercel, you might need to configure their specific settings to recognize that your app is not at the root. For instance, on Netlify, you might set the base option in your netlify.toml build settings, or ensure your index.html is served from the correct subdirectory. However, the Vite base configuration is the primary way to make your build output aware of its deployment path, simplifying the web server’s job.

The most common mistake is forgetting that Vite’s base option is a build-time configuration. If you deploy a Vite app to a subdirectory without setting base, your JavaScript and CSS assets will likely 404 because the browser is looking for them at the root of the domain, not within the subdirectory.

When you use vite build, Vite generates a dist folder. The base option dictates the structure and asset paths within this dist folder. If base is /app/, the dist folder will contain an index.html that references assets like /app/assets/main.js. If you then copy the contents of dist into a subdirectory on your web server (e.g., /var/www/html/my-app/), your web server will correctly serve /my-app/index.html, which then correctly requests /my-app/assets/main.js.

This base setting is crucial for client-side routing as well. If you’re using a router like react-router-dom or vue-router and your app is not at the root, you’ll need to configure your router’s basename (or equivalent) to match Vite’s base setting. For example, in react-router-dom v6:

// App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter basename="/app"> {/* Matches Vite's base */}
      <Routes>
        <Route path="/" element={<div>Home</div>} />
        <Route path="/about" element={<div>About</div>} />
      </Routes>
    </BrowserRouter>
  );
}

This ensures that when a user navigates to /app/about, the router correctly interprets the path within the context of the /app base.

The next problem you’ll encounter is configuring your web server (like Nginx or Apache) to handle requests for your subdirectory correctly, especially if you’re using client-side routing.

Want structured learning?

Take the full Vite course →