Introduction
Vite is Laravel 13's default frontend build tool, replacing Laravel Mix. It offers a blazing-fast dev server with HMR, native ES modules, and optimized production bundles via esbuild and Rollup.
Key Concepts
- Vite dev server: A local HTTP server (usually on port 5173) that serves your source files with Hot Module Replacement in development.
- Entry point: A source file (e.g.,
resources/js/app.js) declared invite.config.jsand compiled into a bundle. @vitedirective: The Blade helper that emits the right<script>and<link>tags for the configured entry points — dev-server URLs in dev, hashed build files in production.- Manifest:
public/build/manifest.json, emitted byvite build, that maps source entries to their hashed production filenames.
Real World Context
Every Laravel 13 project with CSS or JavaScript goes through Vite. Understanding the dev-server flow, the @vite directive, and the manifest is a prerequisite for any frontend work — from Tailwind to Alpine to full Vue/React SPAs.
Deep Dive
Vite is Laravel's default frontend build tool, replacing Laravel Mix. It offers blazing-fast development with Hot Module Replacement (HMR) and optimized production builds.
Why Vite?
| Feature | Vite | Laravel Mix (Webpack) |
|---|---|---|
| Dev Server Start | Near-instant (milliseconds) | Seconds |
| Hot Reload | Instant | Noticeable lag |
| Build Time | Fast (esbuild) | Slower (Webpack) |
| Configuration | Simple | Complex |
| Native ESM | Yes | No |
Project Setup
New Laravel projects already include Vite wired up. Your package.json will look roughly like this — check the exact versions your installer pinned, since they are refreshed with each Laravel release:
json{ "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build" }, "devDependencies": { "@tailwindcss/vite": "^4", "laravel-vite-plugin": "^2", "tailwindcss": "^4", "vite": "^7" } }
Configuration
javascript// vite.config.js import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import tailwindcss from '@tailwindcss/vite'; export default defineConfig({ plugins: [ laravel({ input: [ 'resources/css/app.css', 'resources/js/app.js', ], refresh: true, // Auto-refresh on Blade changes }), tailwindcss(), // Tailwind v4 plugin ], });
The @vite Directive
Include compiled assets in Blade:
blade<!DOCTYPE html> <html> <head> <title>My App</title> @vite(['resources/css/app.css', 'resources/js/app.js']) </head> <body> {{ $slot }} </body> </html>
In Development, this generates:
html<script type="module" src="http://localhost:5173/@vite/client"></script> <link rel="stylesheet" href="http://localhost:5173/resources/css/app.css"> <script type="module" src="http://localhost:5173/resources/js/app.js"></script>
In Production (after npm run build):
html<link rel="stylesheet" href="/build/assets/app-BrYLxZ9n.css"> <script type="module" src="/build/assets/app-DfJk2XMz.js"></script>
Development Workflow
bash# Terminal 1: Start Laravel php artisan serve # Terminal 2: Start Vite dev server npm run dev
Now your browser connects to Laravel at localhost:8000, and Vite serves assets from localhost:5173 with HMR.
CSS with Vite
Tailwind CSS v4 (Default)
Laravel 13 ships with Tailwind v4, which uses a single @import directive — no more @tailwind base/components/utilities, and no tailwind.config.js or postcss.config.js is required:
css/* resources/css/app.css */ @import "tailwindcss"; /* Custom styles */ .btn-primary { @apply px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700; }
Theme customizations now live in CSS via @theme:
css@theme { --color-brand: oklch(0.72 0.15 250); --font-display: "Inter", sans-serif; }
Importing CSS in JavaScript
javascript// resources/js/app.js import '../css/app.css'; // Your JavaScript console.log('App loaded!');
JavaScript with Vite
ES Modules
javascript// resources/js/app.js import './bootstrap'; import { formatDate } from './utils/date'; import Alpine from 'alpinejs'; window.Alpine = Alpine; Alpine.start(); console.log(formatDate(new Date()));
javascript// resources/js/utils/date.js export function formatDate(date) { return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }).format(date); }
Installing NPM Packages
bashnpm install alpinejs npm install axios npm install lodash-es # ES module version
javascript// resources/js/app.js import Alpine from 'alpinejs'; import axios from 'axios'; import { debounce } from 'lodash-es'; window.Alpine = Alpine; window.axios = axios; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; Alpine.start();
Multiple Entry Points
javascript// vite.config.js export default defineConfig({ plugins: [ laravel({ input: [ 'resources/css/app.css', 'resources/js/app.js', 'resources/css/admin.css', 'resources/js/admin.js', ], refresh: true, }), ], });
blade{{-- Main app layout --}} @vite(['resources/css/app.css', 'resources/js/app.js']) {{-- Admin layout --}} @vite(['resources/css/admin.css', 'resources/js/admin.js'])
Production Build
bashnpm run build
This creates optimized assets in public/build/:
- Minified CSS and JavaScript
- Content-hashed filenames for cache busting
- Manifest file for Laravel to resolve paths
public/build/
├── assets/
│ ├── app-BrYLxZ9n.css
│ └── app-DfJk2XMz.js
└── manifest.json
Troubleshooting
blade{{-- Check if Vite is running --}} @if (app()->environment('local')) <p>Make sure to run: <code>npm run dev</code></p> @endif
Common issues:
- Blank page: Vite dev server not running
- CORS errors: Check Vite server URL in config
- Assets not found in production: Run
npm run build
Common Pitfalls
- Running
php artisan servewithoutnpm run dev— Vite's dev server isn't running, so@viterenders 404s for every asset. - Forgetting
@vitein the layout — No script or stylesheet tags are emitted, and the page renders unstyled. - Hardcoding
/resources/...paths in HTML — The source path isn't the production path. Always go through@vite()orVite::asset().
Best Practices
- Always use the
@vitedirective, not manual<script>tags — Only@viteknows whether to target the dev server or the manifest. - Keep entry points minimal — One CSS entry and one JS entry is usually enough. Split only when you genuinely need separate bundles.
- Rely on the dev server for HMR locally —
npm run dev+php artisan serveis the standard two-terminal workflow.
Summary
- Vite replaces Laravel Mix in Laravel 13 and offers HMR, native ESM, and fast builds.
vite.config.jsdeclares entry points;@vite([...])in Blade emits the right tags.- Dev uses the Vite dev server (5173); production uses hashed files in
public/build/. - CSS and JS can be imported with native ES module syntax.
npm run devstarts the dev server;npm run buildproduces the production bundle.