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: August 2026

Getting the Vite config for Laravel projects right is the difference between a smooth development experience and hours of debugging broken assets or slow builds. While Laravel 12 ships with sensible defaults, most production applications require customization to handle CSS preprocessors, third-party libraries, and server-specific constraints. If you are setting up a new application or migrating an older stack, understanding these configuration nuances prevents common pitfalls that stall teams. For developers evaluating their broader stack choices, my overview of Laravel development services covers when this framework makes sense for business-critical systems.

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

Laravel 12 includes Vite out of the box, but the default configuration is intentionally minimal. On any real client project I have shipped since 2024, the base vite.config.js required expansion within the first week of development. The plugin acts as the bridge between Vite's native ESM workflow and Laravel's Blade templating engine.

A standard starting point for a custom application looks like this:

<?php // vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

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: {
            '@': '/resources/js',
        },
    },
});

The input array is where most configuration issues originate. Unlike Webpack Mix, which could auto-discover files, Vite requires explicit entry points. Every CSS file and JS module that needs processing must be listed here. I typically organize entry points by feature for larger applications rather than dumping everything into a single app.js. This keeps initial bundle sizes manageable and allows for code splitting.

The refresh: true option tells the Vite dev server to trigger a full page reload whenever Blade templates change. Without this, you will find yourself manually refreshing after updating views, which defeats the purpose of HMR. For projects using Livewire, this setting is non-negotiable; for pure API backends serving a separate SPA frontend, you can safely omit it.

Source Filesresources/css/app.cssresources/js/app.jsresources/js/components/Blade TemplatesVite Dev Serverlaravel-vite-pluginHMR WebSocketESM TransformPort 5173Browser / PHP@vite() DirectiveHot Module ReloadStyle InjectionPHP Artisan Serve
Vite config for Laravel projects: Asset compilation flow from source files through the dev server to the browser via @vite directive

When working on legal-tech portals or e-commerce platforms, I often add additional entry points for admin panels or vendor dashboards that live in separate directories. Keeping these isolated prevents loading unnecessary JavaScript on public-facing pages, which directly impacts Core Web Vitals scores.

Why is HMR not working in Docker or remote environments?

This is the single most frequent issue I encounter when onboarding developers or deploying staging environments. Hot Module Replacement relies on a WebSocket connection between the browser and the Vite dev server. In local setups where PHP and Vite run on the same host machine, this works automatically. Inside Docker containers, WSL2, or remote EC2 instances, the browser cannot reach localhost:5173 because that address refers to the user's machine, not the container.

The fix requires explicit host configuration in your Vite config:

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

Setting host: '0.0.0.0' binds the Vite server to all network interfaces, making it accessible outside the container. The hmr.host parameter tells the browser which hostname to use for the WebSocket connection. If you access your application via http://localhost:8000, set hmr.host to localhost. If you use a custom domain like myapp.test through Nginx Proxy Manager or Traefik, use that domain instead.

  • Symptom: Page loads but styles/scripts never update without manual refresh.
  • Cause: Browser console shows WebSocket connection refused or timeout errors.
  • Fix: Verify server.host is 0.0.0.0 and hmr.host matches your access URL.
  • Docker Compose: Ensure port 5173 is exposed in your docker-compose.yml service definition.
  • Firewall: On Ubuntu servers running UFW, allow port 5173 for development IPs only.

I have seen teams waste entire sprints assuming HMR was broken due to framework bugs when it was purely a networking misconfiguration. Always check the browser's Network tab for failed WebSocket requests before suspecting the plugin itself.

How should you optimize Vite config for Laravel projects in production?

Development convenience and production performance are opposing goals. Your vite.config.js must handle both contexts without maintaining separate configuration files. Vite uses environment variables and conditional logic to switch behaviors during npm run build.

Configuration AspectDevelopment DefaultProduction OptimizationImpact
Source MapsEnabled (inline)Disabled or hiddenReduces bundle size 20-40%
CSS MinificationDisabledesbuild/lightningcssFaster parse times
Code SplittingMinimalManual chunks + dynamic importsImproves LCP/FCP
Asset NamingOriginal filenamesContent hash ([name]-[hash])Enables aggressive caching
Tree ShakingPartialFull ESM analysisRemoves dead code

For production builds, I always disable source maps unless actively debugging a live issue. Source maps can expose proprietary logic and significantly increase deployment artifact size. Add this to your config:

export default defineConfig(({ mode }) => ({
    plugins: [ /* ... */ ],
    build: {
        sourcemap: mode === 'development',
        rollupOptions: {
            output: {
                manualChunks: {
                    vendor: ['vue', 'axios', 'pinia'],
                },
            },
        },
    },
}));

The manualChunks configuration separates third-party libraries from application code. Vendor bundles change infrequently, allowing browsers to cache them independently. On a recent e-commerce project, this simple change reduced repeat-visit payload by 65% because the Vue/Pinia chunk remained cached across deployments while only the app chunk updated.

Also consider enabling CSS code splitting if your application has distinct sections (public store vs. admin panel). Import CSS dynamically within route components rather than globally to avoid shipping unused styles. This aligns with technical SEO best practices where render-blocking resources directly affect search rankings.

Unoptimized Buildapp-[hash].js (2.4 MB)Vue + Axios + App Code + Source Mapapp-[hash].css (890 KB)All Styles Including Admin PanelTotal: 3.3 MBCache invalidates every deployRender-blocking CSSOptimized Buildvendor-[hash].js (420 KB) — CachedVue + Axios + Piniaapp-[hash].js (380 KB)Application Logic Onlypublic-[hash].css (120 KB)Storefront Styles Onlyadmin-[hash].css (95 KB) — LazyLoaded on DemandInitial Load: 920 KBVendor cached across deploysNo render-blocking admin CSS
Vite config for Laravel projects: Production build comparison showing optimized chunk splitting reducing initial payload by 72%

What are common Vite plugin compatibility issues and fixes?

The Laravel ecosystem moves fast, and plugin versions frequently fall out of sync. As of mid-2026, ensure you are running compatible versions:

  • laravel-vite-plugin: ^2.0 for Laravel 12.x (requires Vite 6.x)
  • @vitejs/plugin-vue: ^6.0 for Vue 3.5+
  • sass/sass-embedded: ^1.80 for modern Sass features
  • tailwindcss: ^4.0 with @tailwindcss/vite plugin

A recurring problem involves PostCSS and Tailwind CSS v4 integration. Tailwind v4 dropped the traditional postcss.config.js approach in favor of a dedicated Vite plugin. If you are upgrading from v3, remove PostCSS entirely and add:

import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
    plugins: [
        tailwindcss(),
        laravel({ /* ... */ }),
    ],
});

Another frequent issue occurs when mixing CommonJS and ESM packages. Some older npm dependencies still ship CJS-only builds. Vite handles this via pre-bundling, but occasionally you need to explicitly include problematic packages:

export default defineConfig({
    optimizeDeps: {
        include: ['legacy-package-name', 'another-cjs-lib'],
    },
    ssr: {
        noExternal: ['problematic-ssr-package'],
    },
});

If you encounter "Failed to resolve import" errors for aliases, verify that your resolve.alias paths are absolute. Relative aliases behave inconsistently across operating systems. Always prefix with / or use path.resolve(__dirname, 'resources/js') for cross-platform safety.

For developers building APIs alongside frontends, remember that Vite only processes frontend assets. Backend API routes remain untouched. If you are designing REST endpoints consumed by your Vite-built SPA, review Laravel API best practices to ensure your backend contract aligns with frontend expectations.

How does Vite compare to Laravel Mix for existing projects?

Many Nepal-based businesses and agencies still maintain Laravel Mix projects from 2020-2023. Migrating to Vite is not automatic and requires deliberate effort. Understanding the trade-offs helps decide whether migration is justified now or can wait.

Current Project Status?New Project / Major RewriteExisting Stable Mix ProjectUse ViteFast HMR • Modern ESM • Active SupportLaravel 12 Default • Future-ProofEvaluate Migration NeedSlow Builds? → MigrateStable & No Pain? → Stay Until EOLMigration Effort: Medium• Replace mix() with @vite()• Rewrite webpack.mix.js → vite.config.js• Update CI/CD build commandsRisk Factors• Custom Webpack plugins may lack Vite equiv• Legacy jQuery/CJS dependencies• Team unfamiliar with ESM/Vite
Decision framework for Vite config for Laravel projects: When to adopt Vite versus maintaining existing Laravel Mix installations

In practice, I recommend Vite for all new Laravel 12 projects and major rewrites. The development feedback loop is measurably faster, especially for Vue/Livewire applications. However, for stable maintenance-mode projects generating revenue, the migration cost rarely justifies itself unless build times exceed 30 seconds or HMR reliability becomes a blocker.

If you do migrate, budget 2-4 hours for a typical medium-complexity application. The biggest time sink is usually rewriting custom Webpack plugins or loaders that have no direct Vite equivalent. Test thoroughly in staging before touching production; asset path changes have broken more deployments than I care to count.

Final Recommendations for Vite Configuration

Getting the Vite config for Laravel projects right requires treating it as infrastructure, not an afterthought. Start with the official Laravel preset, then customize incrementally based on actual pain points rather than anticipated needs. Always test HMR in your exact development environment (Docker, WSL, native) before assuming it works. For production, implement chunk splitting and disable source maps from day one — retrofitting these later invites deployment regressions.

Remember that Vite configuration is just one layer of a performant Laravel application. Asset bundling interacts with server configuration, CDN strategy, and caching headers. If you are building business-critical systems and want to ensure your entire stack is optimized, reach out to discuss your project. Whether you need a fresh build or help untangling a legacy asset pipeline, practical experience beats documentation guessing every time.

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

Quick Contact Options
Choose how you want to connect me: