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| Output | What it is | How to treat it |
|---|---|---|
index.html | Entry document naming the hashed assets | Short cache lifetime; it changes every deploy |
assets/*.js | Entry chunk plus one file per dynamic import | Immutable, cache forever |
assets/*.css | Extracted styles, hashed | Immutable, cache forever |
public/* | Copied verbatim to the build root | Referenced by absolute path, no hash |
| Source maps | Off by default; upload to an error tracker if enabled | Never 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 onedist/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.localand other local files are gitignored; commit an.env.exampleso 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 inindex.htmlreturn 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" }] }| Asset | Cache-Control | Why |
|---|---|---|
index.html | no-cache | It names the current hashed files, so it must be revalidated |
assets/*.js, *.css | public, max-age=31536000, immutable | The filename changes with the content |
| Fonts and icons with a hash | immutable | Same reasoning; no revalidation needed |
| Uploaded images without a hash | A short max-age or a CDN purge step | The URL stays the same when the content changes |
| API responses | Set by the API, not the static host | Never 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.
Related
Performance, rendering and SSR basics Setting up a Vue project with Vite
Last refreshed 2026-09-18.