Production builds, environments and deployment

Read the build output, split code by route, handle environment values and a base path, then host the bundle with the right caching headers.

What the build produces

npm run build
# dist/index.html
# dist/assets/index-a1b2c3.js        entry chunk
# dist/assets/CartView-d4e5f6.js     route chunk from () => import()
# dist/assets/index-7g8h9i.css

npm run preview              # verify the real build before it ships
npx vite-bundle-visualizer   # see what is inside each chunk
OutputWhat it isHow to treat it
index.htmlEntry document naming the hashed assetsShort cache lifetime; it changes every deploy
assets/*.jsEntry chunk plus one file per dynamic importImmutable, cache forever
assets/*.cssExtracted styles, hashedImmutable, cache forever
public/*Copied verbatim to the build rootReferenced by absolute path, no hash
Source mapsOff by default; upload to an error tracker if enabledNever serve them publicly
  • A dynamic import() becomes its own chunk, so a route is only downloaded when visited. Check the route chunks: a surprisingly large one means something heavy is imported at the top of a view.
  • Vite warns above 500 kB per chunk. Treat the warning as a question — usually a charting library, an editor or a locale file that could be loaded on demand.
  • The entry chunk is on the critical path for every page, so keeping imports out of it matters more than shaving the total size.

Environment values and the base path

// src/config.js — one place that reads environment values
export const config = {
  apiBase: import.meta.env.VITE_API_BASE ?? '/api',
  title: import.meta.env.VITE_APP_TITLE ?? 'Guides',
  isProd: import.meta.env.PROD
}
// vite.config.js — serving from a subdirectory
export default defineConfig({
  base: '/app/',      // asset URLs in index.html become /app/assets/...
  build: { sourcemap: false }
})
  • Vite replaces import.meta.env.VITE_* at build time, so one dist/ cannot be repointed at a different API by changing a variable afterwards — rebuild instead.
  • Build once per environment. A staging artefact and a production artefact are different files, and that is normal rather than a smell.
  • .env.local and other local files are gitignored; commit an .env.example so the required keys are documented for the next developer.
  • If the app is served from a subdirectory, set base. Without it the absolute asset paths in index.html return 404 even though the HTML loads.
  • Anything named VITE_ is in the bundle in plain text. Treat every one of them as public.
⚠️
With history mode, /guides/intro exists only as a client-side route. The host must rewrite unknown paths to index.html; without that rewrite, deep links 404 on refresh even though navigation inside the app works.

Hosting, rewrites and caching

# Netlify: public/_redirects
/*  /index.html  200

# nginx
location / { try_files $uri $uri/ /index.html; }

# Vercel: vercel.json
{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }
AssetCache-ControlWhy
index.htmlno-cacheIt names the current hashed files, so it must be revalidated
assets/*.js, *.csspublic, max-age=31536000, immutableThe filename changes with the content
Fonts and icons with a hashimmutableSame reasoning; no revalidation needed
Uploaded images without a hashA short max-age or a CDN purge stepThe URL stays the same when the content changes
API responsesSet by the API, not the static hostNever cache a per-user response at the edge
  • No-cache HTML plus immutable hashed assets gives instant deploys with no stale-asset bugs. Getting this pair wrong is what produces "works for me, broken for the client".
  • Enable Brotli or gzip at the host; built JavaScript compresses to a fraction of its size.
  • Build and deploy from CI so the artefact is reproducible and traceable to a commit, rather than uploaded from a laptop.
  • Before calling a deploy done, check a refreshed deep link, a hard refresh, the API base URL in the network tab, and one error page.
  • Verify the custom domain, not the provider's preview URL: a correct base path on one and a broken one on the other is a common outcome.

FAQ

Where should I host a Vue single-page app?
Any static host: the build is a folder of files. Pair it with an API on a separate origin, or serve both from the same domain behind a reverse proxy so cookies and CORS stay simple.
Do I need server rendering for SEO?
Only if the pages must be indexed by a crawler that does not run JavaScript, or if the first paint on a slow connection is a product requirement. Ordinary marketing pages and authenticated dashboards rarely justify the added complexity.

Performance, rendering and SSR basics Setting up a Vue project with Vite

Last refreshed 2026-09-18.