Introduction
The dev server is great for local work, but production needs hashed, minified, cache-friendly bundles served by your web server or a CDN. This lesson walks through the production build pipeline and the pieces you need to get right before shipping.
Key Concepts
- Build output: The static files emitted by
npm run build, written topublic/build/. - Manifest: A JSON file that maps source entry points (like
resources/js/app.js) to their hashed production filenames. Laravel's@vitedirective reads this to generate the correct<script>and<link>tags. - Cache busting: Each build produces filenames with content hashes (
app-DfJk2XMz.js), so the browser's cache is invalidated automatically when the code changes. - Asset URL: The public URL prefix from which assets are served — often the app origin, sometimes a CDN.
Real World Context
If you deploy without running npm run build, Laravel will try to connect to the Vite dev server at localhost:5173 — users see broken pages. Every deploy pipeline should build assets in CI and ship the public/build/ output alongside the PHP files.
Deep Dive
Running the Production Build
bashnpm ci # Install pinned dependencies npm run build # Emits public/build/*
The output looks like:
public/build/
├── assets/
│ ├── app-BrYLxZ9n.css
│ └── app-DfJk2XMz.js
└── manifest.json
The manifest tells Laravel which hashed file corresponds to each source entry:
json{ "resources/js/app.js": { "file": "assets/app-DfJk2XMz.js", "src": "resources/js/app.js", "isEntry": true, "css": ["assets/app-BrYLxZ9n.css"] } }
When @vite(['resources/js/app.js']) runs in production, it reads the manifest and renders <script src="/build/assets/app-DfJk2XMz.js"> plus the associated CSS <link>.
CI Pipeline Sketch
A typical GitHub Actions step:
yaml- name: Build frontend run: | npm ci npm run build - name: Deploy run: rsync -avz public/build/ user@server:/var/www/app/public/build/
The key idea: build assets in CI, then ship the public/build/ directory. Never run npm run build on production with limited memory — large builds can OOM tiny VMs.
Serving Assets from a CDN
To serve public/build/ from a CDN, set ASSET_URL in your .env:
envASSET_URL=https://cdn.example.com
Now @vite(...) emits https://cdn.example.com/build/assets/app-DfJk2XMz.js instead of a relative path. Upload the public/build/ folder to the CDN during deploy and you get edge caching for free.
Detecting a Missing Build
If public/build/manifest.json is missing in production, @vite throws ViteManifestNotFoundException. Catch this failure early in deployment health checks rather than letting end users hit a 500 page.
Common Pitfalls
- Committing
public/build/to git — It's build output. Let CI regenerate it on every deploy. - Forgetting
npm ciand usingnpm install—npm cirespects the lockfile exactly;installmay drift and produce different bundles across environments. - Running the dev server on production —
npm run devis for local work only. Production must use the build output.
Best Practices
- Pin your lockfile — Commit
package-lock.json(orpnpm-lock.yaml) and usenpm ciin CI so builds are reproducible. - Build once, deploy many — If you deploy to staging and then promote to production, reuse the same built artifacts rather than rebuilding.
- Monitor bundle size — Add a
size-limitcheck in CI so a runaway import doesn't silently ship a 5 MB bundle.
Summary
npm run buildemits hashed files topublic/build/and a manifest Laravel reads.- The manifest is how
@vitemaps source entries to final filenames. - Build in CI, deploy the output, never run the dev server on production.
- Point
ASSET_URLat a CDN to serve assets from the edge.