When you open a modern Laravel project, the vite.config.js file in the root can be confusing at first; in this post I'll explain exactly what the Vite Laravel pairing does, why your browser updates instantly during development, and what that mysterious manifest.json file is for when you ship to production. Since Laravel 9.19, the default build tool is no longer the Webpack-based Laravel Mix but Vite. Once the mental model clicks, managing your CSS and JavaScript assets becomes much more pleasant.
What Vite is and why it became Laravel's default
Vite is a frontend build tool created by Evan You, the creator of Vue. It combines two jobs in one tool: a very fast dev server during development, and an optimized Rollup-based bundler for production. The difference from older tools is that in development mode it doesn't bundle your code into one big package upfront. Instead it leverages the browser's native ES modules support and serves files only as they're requested. The result: even as your project grows, the dev server starts almost instantly.
The integration with Laravel is provided by the laravel-vite-plugin package. This plugin tells Vite which files are entry points and communicates with the @vite directive you use on the Blade side.
Installation and basic configuration
Vite comes pre-installed in a new Laravel project. Two commands are enough to install dependencies and start the dev server:
npm install
npm run dev
The vite.config.js file in the project root defines the entry points:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
],
});
Here the input array specifies the main files Vite will track. The refresh: true option automatically reloads the browser whenever you save Blade templates, route files, or other PHP sources. On the Blade side, you include your assets like this:
<!DOCTYPE html>
<html>
<head>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
The @vite directive is the magic part: it changes its behaviour depending on the environment. In development it points to the Vite server, and in production to the compiled files.
HMR in development: why the browser updates instantly
HMR (Hot Module Replacement) is the mechanism that, when you save a file, injects only the changed module into the browser without reloading the whole page. When npm run dev runs, Vite opens a dev server on port 5173 by default. At the same time, a small file named public/hot is created in the project root, containing the address of the dev server.
Each time a page is rendered, the @vite directive first checks whether this public/hot file exists. If it does, it doesn't serve the assets itself; instead it points the tags at the Vite server and a WebSocket connection is established. When you change a CSS file the styles update without the page reloading at all; when you edit a single JavaScript module, only that module changes. This greatly speeds up the development flow because it preserves application state (form inputs, open modals).
- No full reload: only the changed module updates, and page state is preserved.
- Instant feedback: you see the change the moment you hit save.
- Automatic injection: new
importdependencies you add are resolved on the fly.
Bundling and the manifest in production
When you're ready to go live, you run npm run build. At this stage Vite uses Rollup to combine all modules, eliminate dead code (tree-shaking), minify the JavaScript and CSS, and add a content-based hash to the file names. The output is written to the public/build folder.
That hash is critical: instead of app.js you get a name like app-4ed1f8c2.js. Whenever the file's content changes, the hash changes too. This fundamentally solves the problem of the browser and CDN serving an old version from cache (cache busting). But you can't write that random hash by hand in your Blade template. This is where the manifest comes in.
At the end of the build, Vite generates public/build/.vite/manifest.json. This file maps source file names to compiled outputs. A simplified example looks like this:
{
"resources/js/app.js": {
"file": "assets/app-4ed1f8c2.js",
"isEntry": true,
"css": ["assets/app-1b2c3d4e.css"]
}
}
In a production environment the @vite(['resources/js/app.js']) directive can't find the public/hot file, so it reads the manifest. From the source path it finds the real hashed file name and generates the correct <script> and <link> tags. You write the source path, and Laravel resolves the right physical file through the manifest. That's why the public/build folder must always be deployed to the server.
Common problems and their fixes
The most common error in a Vite Laravel setup is the "Unable to locate file in Vite manifest" message in production. This is almost always caused by npm run build not having been run, or the public/build folder being missing on the server. A few practical points:
- Build during deploy: always run
npm run buildbefore going live and ship thepublic/buildfolder. - Don't commit the
public/hotfile: if it lingers afternpm run dev, production will wrongly keep looking for the dev server. Shut down dev cleanly withCtrl+C. Vite::asset()for static assets: to point to files like images, place them underresources/and use the helper, or use the public folder directly.- Behind SSL/proxy: if the HMR connection can't be established in Docker or a remote dev environment, you may need to adjust the
server.hmrsettings invite.config.jsfor your host.
Frequently Asked Questions
What's the difference between Vite and Laravel Mix?
Laravel Mix was a wrapper built on top of Webpack and was slow in development because it recompiled the whole bundle from scratch. Vite uses native ES modules in development so it starts instantly, and optimizes with Rollup in production. Since Laravel 9.19, Vite is the default for new projects; Mix is still supported but Vite is recommended for new work.
Should I add the public/build folder to Git?
Usually no. This folder is the compiled output and is typically added to .gitignore; instead npm run build runs during the deploy step. However, if you're on shared hosting where you can't run the build step, committing the folder and shipping it directly is also a valid strategy.
Should npm run dev and php artisan serve run at the same time in development?
Yes. php artisan serve (or your web server) serves the PHP application, while npm run dev opens the Vite server in a separate terminal and provides HMR. When both run together, Blade pages pull their assets from the Vite server.
Is your frontend build process slowing down or are you hitting Vite manifest errors? I can help with asset pipeline setup, deploy automation, and performance in Laravel projects. Get in touch and let's talk about your project.