Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Vite Config for Laravel Projects

By Kokil Thapa | Last reviewed: September 2026

Your laravel vite setup either saves hours every week or quietly wastes them. Styles fail to load after deploy. Hot reload dies inside Docker. Production bundles ship at two megabytes because nobody touched vite.config.js after laravel new. Laravel 13 and Laravel 12 ship Vite by default, but the stock config is a starting point — not a finished pipeline. This guide walks through the config I use on production Laravel apps: entry points, the default dev-server port, HMR over Docker, build tuning, and when Mix still makes sense. If you are weighing the broader stack, see my notes on Laravel development for business-critical systems and how asset tooling fits the full delivery picture.

How do you set up the base Vite config for Laravel projects?

Laravel 12 and Laravel 13 include Vite out of the box. The default file is intentionally small. On real client projects, I expand it within the first sprint — extra admin bundles, Vue islands, or a separate vendor dashboard almost always appear.

The laravel-vite-plugin bridges Vite's ESM dev server and Laravel's @vite Blade directive. Official reference: the Laravel Vite documentation.

Starter config with Vue and path aliases

A typical starting point for a custom Laravel app with Vue 3 looks like this:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
import path from 'node:path';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
    resolve: {
        alias: {
            '@': path.resolve(process.cwd(), 'resources/js'),
        },
    },
});

The input array is where most misconfiguration starts. Unlike Laravel Mix, Vite does not auto-discover files. Every CSS and JS entry you want compiled must be listed explicitly. I split entries by surface area on larger apps — public storefront, admin panel, vendor portal — so public pages never download admin JavaScript. That directly helps Core Web Vitals and Laravel SEO.

Set refresh: true so Blade template edits trigger a full reload. Livewire and Alpine-heavy apps need this. Pure API backends with a separate SPA can omit it. For Livewire-specific front-end choices, compare Livewire 3 versus Inertia before locking entry points.

Laravel Vite Asset PipelineSource Filesresources/cssresources/jsBlade viewsVite Dev Serverlaravel-vite-pluginHMR WebSocketDefault port 5173Browser@vite directiveHot reloadStyle injectionProduction: npm run buildOutputs hashed files to public/buildManifest read by Laravel at runtime
Laravel vite flow: source assets compile through the Vite dev server on port 5173, then ship as hashed files after npm run build

On booking systems like Adventure Third Pole Trek, I keep admin Livewire assets in separate entries. Public trek pages stay lean. That pattern maps cleanly to any multi-role Laravel app.

Why is HMR not working when the Vite dev server uses port 5173?

This is the issue I see most often when onboarding developers. Hot Module Replacement needs a WebSocket from the browser back to Vite. On a native Linux or macOS setup, that usually just works. Inside Docker, WSL2, or a remote staging VM, the browser tries to reach the wrong host.

Per the official Vite server options, the default port is 5173. Laravel's dev script runs vite alongside php artisan serve. If HMR fails, the page loads but assets never update until you hard-refresh.

Fix server and HMR host settings

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
    server: {
        host: '0.0.0.0',
        port: 5173,
        strictPort: true,
        hmr: {
            host: 'localhost',
        },
    },
});

host: '0.0.0.0' binds Vite to all interfaces inside the container. hmr.host tells the browser which hostname to use for the WebSocket. Match it to how you open the app — localhost, myapp.test, or your staging domain.

  1. Symptom: Styles and scripts never hot-reload.
  2. Check: Browser console for WebSocket errors on port 5173.
  3. Docker: Expose port 5173 in docker-compose.yml. See Docker Compose for local Laravel.
  4. Sail: Use Laravel Sail port forwarding conventions if you run the full stack in containers.
  5. Firewall: On Ubuntu with UFW, allow 5173 only for dev IPs — never on production.

I have watched teams burn days blaming Laravel when the fix was three lines in server.hmr. Open DevTools, filter Network by WS, and confirm the handshake succeeds before you touch application code.

How should you optimize Vite config for Laravel production builds?

Development speed and production weight pull in opposite directions. One vite.config.js should serve both via mode from defineConfig. Run npm run build in CI before deploy — the same pattern I use in GitLab CI pipelines for Laravel.

SettingDevelopmentProductionWhy it matters
Source mapsInline or enabledOff or hiddenSmaller artifacts, less exposed logic
CSS minifyOffesbuild or lightningcssFaster parse, better LCP
Code splittingMinimalmanualChunks + dynamic import()Cache vendor libs across deploys
File namesOriginalContent hash suffixLong-cache static assets safely
Tree shakingPartialFull ESM analysisDrops dead imports

Production build block

export default defineConfig(({ mode }) => ({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
    build: {
        sourcemap: mode === 'development',
        rollupOptions: {
            output: {
                manualChunks: {
                    vendor: ['vue', 'axios', 'pinia'],
                },
            },
        },
    },
}));

I disable production source maps unless I am actively debugging a live incident. They inflate deploy size and can expose business logic. manualChunks isolates libraries that change rarely. Browsers cache the vendor file across releases while only the app chunk updates.

Split CSS by route or layout when admin styles differ from the storefront. Dynamic import() for admin-only Vue pages keeps public CSS off checkout flows. That pairs well with speed optimization work on Nepali e-commerce sites. For a live example of a lean Laravel storefront, see Quick And Easy Nepalese Grocery.

Production Build OutputUnoptimizedapp.js — 2.4 MBapp.css — 890 KBInitial load ~3.3 MBFull cache bust each deployOptimizedvendor.js — 420 KBapp.js — 380 KBpublic.css — 120 KBadmin.css — lazy loadInitial load ~920 KBVendor cached across releases
Laravel vite production tuning: manualChunks and CSS splitting cut initial payload versus a single monolithic bundle

What are common Vite plugin compatibility issues in Laravel?

The Laravel front-end stack moves quickly. Pin versions in package.json and upgrade deliberately. Target these anchors in 2026:

  • Node.js: 26 LTS for local dev and CI (24 LTS still supported)
  • npm: 12
  • Vite: 8.x
  • Laravel: 13.x on PHP 8.3+, or Laravel 12 on PHP 8.2+
  • Vue: 3.x with @vitejs/plugin-vue matched to your Vite major

Tailwind CSS v4 with the Vite plugin

Tailwind v4 prefers the dedicated Vite plugin over a standalone PostCSS pipeline. Remove legacy PostCSS config and add:

import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
    plugins: [
        tailwindcss(),
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
});

For Blade-only interactivity without Vue, Alpine.js with Blade keeps the JS surface small. Fewer plugins means fewer version conflicts.

Legacy CommonJS packages

Older npm packages still ship CommonJS-only builds. Vite pre-bundles most of them. When imports fail, force inclusion:

export default defineConfig({
    optimizeDeps: {
        include: ['legacy-package-name'],
    },
});

Alias paths must resolve to absolute locations. Use path.resolve(process.cwd(), 'resources/js') instead of relative strings that break across Windows and Linux CI runners. Validate JSON config during setup with the JSON formatter tool if you generate manifest snippets or test fixtures by hand.

Vite only compiles front-end assets. Your API layer stays in PHP. If a Vue or Alpine SPA consumes Laravel endpoints, align contracts with Laravel API best practices before you wire fetch calls in resources/js.

How does Vite compare to Laravel Mix for existing projects?

Many agencies still maintain Mix projects from 2020–2023. Migration is manual. The trade-off is dev speed versus migration cost.

Read the dedicated comparison at Laravel Mix versus Vite and the broader Vite versus Webpack overview. For custom multi-entry setups, see Laravel Vite config for custom asset bundles.

Vite or Mix?Project status today?New or major rewriteStable Mix appUse laravel viteFast HMR, ESM, Laravel 12 defaultEvaluate migrationMigrate if builds exceed 30 secondsMigration checklistmix() to @vite(), rewrite configUpdate CI npm run build stepWatch for blockersCustom Webpack loadersLegacy jQuery CJS plugins
When to adopt laravel vite versus keeping Laravel Mix on maintenance-mode applications

I recommend Vite for every new Laravel 13 or Laravel 12 project. HMR feedback is noticeably faster on Vue and Livewire apps. Stable revenue-generating Mix apps can wait unless build times or developer friction justify the switch.

Budget two to four hours for a medium-complexity migration. Custom Webpack loaders without Vite equivalents eat most of that time. Test in staging first — wrong asset paths break more deploys than PHP bugs. Follow the Laravel production deployment checklist and wire builds through npm scripts in CI. New Laravel 12 apps should skim what changed in Laravel 12 before changing defaults.

How do you wire Vue into a Laravel Vite pipeline?

Most Laravel apps I ship pair Blade with Vue islands or a small SPA shell. Install Vue 3, add @vitejs/plugin-vue, and register Vue in your JS entry:

import { createApp } from 'vue';
import ExampleComponent from './components/ExampleComponent.vue';

const app = createApp({});
app.component('example-component', ExampleComponent);
app.mount('#app');

Mount points live in Blade. Pass initial data via @json() props, not hard-coded globals. The full walkthrough lives in Vue with Laravel setup guide. For greenfield work, web development services often include asset pipeline setup alongside backend delivery.

Dev vs ProductionDevelopmentnpm run devVite on port 5173HMR over WebSocketNo public/build output@vite reads dev serverProductionnpm run buildHashed files in public/buildmanifest.json generatedNo dev server runningCDN can cache /build assetsDeploy
Laravel vite in development serves from port 5173; production reads hashed assets from public/build via manifest.json

Key Takeaways

  • List every CSS and JS entry in laravel-vite-plugin input — Vite will not discover files for you.
  • Set server.host to 0.0.0.0 and match hmr.host to your browser URL when using Docker or remote dev.
  • Remember the default Vite dev port is 5173; expose it in Compose and restrict it on servers.
  • Disable production source maps and split vendor chunks before launch — retrofitting is painful.
  • Use Vite for new Laravel 12 and 13 projects; migrate Mix apps only when build pain justifies the hours.
  • Run npm run build in CI and verify public/build/manifest.json exists before every deploy.

People Also Ask

What is the default Vite dev server port for Laravel?

Vite uses port 5173 by default. Laravel's composer run dev script starts Vite alongside the PHP server. Override it with server.port in vite.config.js if 5173 is taken locally.

Where does Laravel store Vite build output?

Production builds write hashed CSS and JS files to public/build plus a manifest.json file. Laravel reads that manifest when rendering @vite directives. Commit the build output only if your deploy server lacks Node.js — otherwise build in CI.

Can you use Vite with Livewire and Alpine without Vue?

Yes. Keep a single JS entry that imports Alpine or Livewire hooks. You still benefit from fast HMR for CSS and small JS changes. Enable refresh: true so Blade edits reload automatically.

Do you need Node.js on the production Laravel server?

No, if CI runs npm run build and deploys the public/build artifacts. Many VPS setups I maintain compile assets in GitLab CI and rsync only PHP plus built assets — the same approach described in zero-downtime Deployer releases.

Ship a Laravel asset pipeline that stays fast after launch

Treat your laravel vite config as infrastructure, not a one-time scaffold. Start from Laravel defaults, then tune entries, HMR, and production chunks when real pain appears — not on day one speculation. Test hot reload in your actual dev environment before assuming it works. Build in CI, cache vendor chunks, and keep admin assets off public pages.

Vite is one layer in a performant stack. It interacts with CDN headers, PHP-FPM, and page-weight budgets. If you want help auditing an existing pipeline or migrating from Mix, contact us about your Laravel project. You can also reach out directly with your current vite.config.js — practical review beats guessing from docs alone.

Frequently Asked Questions

The primary configuration file is vite.config.js located in your project root. It exports a defineConfig object containing the plugins array with laravel-vite-plugin and specifies input paths for CSS and JavaScript entry points.

Pass an array of strings to the input option within the laravel plugin configuration in vite.config.js. List every CSS and JS file you need bundled separately, such as resources/css/app.css, resources/css/admin.css, resources/js/app.js, and resources/js/admin.js. This generates distinct hashed assets for each entry point, allowing you to load only what specific pages or layouts require without shipping unused code to the browser.

This typically happens when accessing the Laravel app via IP address or non-localhost domain while Vite runs on localhost:5173. Configure server.hmr.host in vite.config.js to match your development domain, or set server.origin to your full local URL. In my experience working on production Laravel applications, this misconfiguration is the most common blocker when developers use Docker containers or virtual hosts instead of the standard php artisan serve command.

Yes. Install tailwindcss and @tailwindcss/vite, then add the Tailwind Vite plugin before the Laravel plugin in vite.config.js. Import your main CSS file directly in your JavaScript entry point rather than using the traditional PostCSS pipeline. Tailwind v4 uses native CSS imports and no longer requires a tailwind.config.js file by default, though you can still create one for custom theme extensions if needed.

Run npm run build during your deployment process before swapping release symlinks. The manifest.json file is generated in public/build/ and maps original filenames to versioned hashes. If using Deployer 7, ensure the build task executes on the deploy runner or remote server with Node.js installed. On projects I maintain, I commit built assets as artifacts from GitLab CI so production servers never need Node, eliminating this failure point entirely.

Use @vite('resources/js/app.js') for Vite projects and mix('js/app.js') for legacy Webpack Mix. The @vite directive automatically injects modulepreload links and handles HMR client injection during development. Never mix both directives in the same project. When migrating older Laravel applications, replace all mix() calls and remove webpack.mix.js only after confirming Vite builds succeed and all asset references resolve correctly in staging.

Set server.https to true or provide a key/cert path object in vite.config.js. Alternatively, use the @vitejs/plugin-basic-ssl package for self-signed certificates without manual key generation. Update your APP_URL in .env to include https:// so Laravel generates correct asset URLs. Without matching protocols between your app and Vite dev server, browsers block HMR WebSocket connections due to mixed-content security policies, breaking hot module replacement entirely.

Reference images using relative imports in CSS or JavaScript, or use the @asset Blade directive for static references. Vite only processes files imported through the module graph or explicitly listed in input paths. Images referenced via raw URL strings in Blade templates bypass Vite's hashing pipeline. In practice, I move frequently used images into resources/images and import them in CSS background-url declarations so they receive proper cache-busting hashes during production builds.

Add a build.rollupOptions.output.manualChunks function in vite.config.js to separate vendor libraries from application code. Return named chunks based on module IDs, such as grouping vue, pinia, and axios into a vendor chunk. This prevents invalidating the entire vendor bundle when application code changes. For legal-tech portals I have built with heavy form libraries, this reduced initial payload sizes significantly and improved cache hit rates for returning users on slower Nepal mobile networks.

Yes, but configuration differs. For Inertia, add ssr.enabled and ssr.entry to the laravel plugin options pointing to your SSR entry file. For Livewire Volt or standalone SSR, configure a separate Vite build targeting Node. Note that SSR adds operational complexity requiring a persistent Node process in production. For most Nepal-based client projects with limited DevOps capacity, I recommend starting with SPA mode and adding SSR only after measuring actual SEO or performance needs.

Typically 300MB to 800MB depending on project size and dependencies. Large applications with many Vue components or heavy icon libraries may exceed 1GB. If builds fail on low-memory servers, increase Node heap size via NODE_OPTIONS=--max-old-space-size=2048 before running npm run build. On shared EC2 instances hosting multiple sister sites, I schedule builds sequentially rather than concurrently to prevent OOM kills during deployment pipelines.

Files not imported anywhere in your dependency graph are automatically excluded. To explicitly prevent processing, do not list them in the input array. For third-party assets that must remain unhashed in public/, place them outside resources/ and reference them with absolute paths. Vite only transforms files under resources/ that are reachable through imports. This keeps vendor-provided PDFs or legacy scripts untouched while maintaining cache busting for your own compiled assets.

Node.js 22 LTS or 20 LTS.

Enable verbose logging with --debug flag and capture full stderr output. Common causes include missing environment variables, incompatible package versions, or insufficient memory. Pin exact dependency versions in package-lock.json and run npm ci instead of npm install. In GitLab CI pipelines I configure for client projects, I add a dedicated lint stage before build to catch syntax errors early, reducing wasted compute time on doomed deployments.

Yes, significantly. Cold starts drop from 10+ seconds to under 2 seconds, and HMR updates occur in milliseconds rather than seconds. Production builds also benefit from Rollup's tree-shaking and native ESM output. However, migration effort varies based on existing Webpack customization. For new Laravel 12 projects, always choose Vite. For legacy systems with complex Mix configurations, budget adequate testing time before switching to avoid regressions in asset handling or third-party integrations.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: