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.

Laravel Vite Config for Custom Asset Bundles

By Kokil Thapa | Last reviewed: August 2026

Default Laravel Vite configurations bundle everything into a single application chunk, which quickly becomes a performance bottleneck as your project grows. Proper Laravel Vite config for custom asset bundles solves this by splitting vendor libraries, admin panels, and public-facing assets into separate, cacheable files. This approach is essential for any production Laravel application serving distinct user experiences or integrating heavy third-party libraries. If you are building complex systems like those described in my Laravel development services, mastering this configuration prevents shipping megabytes of unused code to every visitor.

How do you define multiple entry points in Laravel Vite config for custom asset bundles?

The foundation of any custom bundling strategy is moving beyond the single resources/js/app.js entry point. In Laravel 12 with Vite 6.x, the vite.config.js file accepts an array of inputs via the laravel() plugin. This tells Vite to treat each file as an independent root of the dependency graph, generating separate output hashes for each.

On a recent legal-tech portal I built, we needed a lightweight public site and a heavy admin dashboard with rich text editors and data tables. Bundling them together meant public visitors downloaded 400KB of admin-only JavaScript. Splitting the entry points solved this immediately.

// 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', // Public frontend 'resources/js/admin.js', // Admin panel 'resources/css/admin.css', // Admin styles 'resources/js/vendor-charts.js', // Heavy charting lib ], refresh: true, }), vue(), ], });

Each entry point generates its own manifest record. When you use the @vite() directive in Blade, you reference only the specific entry needed for that page context:

  • @vite(['resources/css/app.css', 'resources/js/app.js']) on public pages
  • @vite(['resources/css/admin.css', 'resources/js/admin.js']) in admin layouts
  • @vite(['resources/js/vendor-charts.js']) only on analytics dashboards

This separation ensures that changes to admin code never invalidate the public browser cache, and vice versa. It also allows you to apply different optimization strategies per bundle, such as transpiling legacy admin code while keeping public code modern.

Multiple Entry Points Architectureresources/js/app.jsresources/js/admin.jsresources/js/vendor-charts.jsPublic BundleAdmin BundleVendor Bundleapp-[hash].js (45KB)admin-[hash].js (320KB)charts-[hash].js (180KB)Each entry produces independently cached output files
Multiple entry points in Laravel Vite config for custom asset bundles create separate, cacheable output files for each application context.

How do you split vendor chunks using manualChunks in Laravel Vite?

Entry points alone don't solve shared dependency bloat. If both app.js and admin.js import Vue and Axios, Vite will duplicate those libraries in both bundles by default. The manualChunks option in Rollup's output configuration gives you explicit control over how shared modules are extracted.

In practice, I configure this to isolate large, stable vendor libraries into their own chunks. This means updating Vue or Axios invalidates only the vendor chunk hash, not your application code hashes. For clients paying for bandwidth in Nepal where CDN costs matter, this distinction directly affects hosting bills.

// vite.config.js (inside defineConfig) build: { rollupOptions: { output: { manualChunks(id) { if (id.includes('node_modules')) { // Separate Vue ecosystem into its own chunk if (id.includes('vue') || id.includes('@vue') || id.includes('pinia')) { return 'vendor-vue'; } // Separate heavy UI libraries if (id.includes('chart.js') || id.includes('quill')) { return 'vendor-heavy'; } // Everything else in node_modules return 'vendor-core'; } }, }, }, },

This function runs for every module in the dependency graph. Returning a string name assigns that module to a named chunk. Modules returning the same name are bundled together. Be specific with your matching logic; overly broad patterns can accidentally group unrelated packages.

A common mistake is trying to match exact package names without accounting for nested paths. Always use includes() with partial paths rather than strict equality checks. Test your configuration with npm run build and inspect the public/build/assets/ directory to verify chunks are splitting as expected. The generated manifest.json will show which chunks each entry point depends on.

What is the difference between automatic and manual chunk splitting in Laravel Vite?

Understanding when to use automatic versus manual splitting prevents over-engineering. Vite's default code-splitting handles dynamic imports (import('./module')) automatically, creating lazy-loaded chunks at runtime. Manual chunking via manualChunks operates at build time, reorganizing static imports before output generation.

CriteriaAutomatic SplittingManual Chunking
TriggerDynamic import() callsmanualChunks function in Rollup config
TimingRuntime (browser fetches on demand)Build time (fixed output files)
Best forRoute components, modals, feature flagsVendor libraries, shared utilities, admin/public separation
Cache impactNew chunk hash per lazy module changeStable vendor hashes independent of app code
Configuration effortZero (works out of box)Requires testing and maintenance
RiskWaterfall requests if overusedOver-splitting increases HTTP requests

For most Laravel projects, combine both approaches. Use manual chunking for stable vendor dependencies and architectural boundaries (admin vs public). Use automatic splitting for route-level code within those boundaries. This hybrid approach balances cache efficiency with request count.

When working on eCommerce platforms, I typically manually chunk payment gateway SDKs and product image galleries since they're large but only needed on specific pages. The core shop navigation stays in the main bundle. This pattern keeps category pages fast while still supporting rich checkout experiences.

Automatic SplittingManual ChunkingStatic imports bundled togetherMain BundleLazy Chunk ALazy Chunk BTriggered by import() at runtimeHash changes when lazy module changesAll imports analyzed at build timeApp CodeVendor CoreVendor VueGrouped by manualChunks functionVendor hash stable across app deploysUse both: manual for vendors, automatic for routes
Automatic splitting handles lazy-loaded routes while manual chunking stabilizes vendor caches in Laravel Vite builds.

How do you optimize CSS extraction for separate Laravel Vite bundles?

CSS deserves the same bundling discipline as JavaScript. By default, Vite extracts CSS per entry point, but shared component styles can still leak across bundles. Explicit CSS entry points combined with PostCSS configuration ensure each bundle carries only its required styles.

Define separate CSS entries alongside your JavaScript entries as shown earlier. For shared design tokens (colors, spacing, typography), create a resources/css/shared/tokens.css file imported at the top of both app.css and admin.css. Vite deduplicates identical content during extraction, so tokens don't double in size.

/* resources/css/app.css */ @import './shared/tokens.css'; @import './components/buttons.css'; @import './layouts/public.css'; /* resources/css/admin.css */ @import './shared/tokens.css'; @import './components/data-tables.css'; @import './layouts/dashboard.css';

For Tailwind CSS users, configure separate content arrays per bundle if using multiple Tailwind configs. However, in most Laravel projects, a single Tailwind config scanning all Blade templates works fine because PurgeCSS removes unused classes at build time regardless of entry point. The real win is keeping admin-specific utility overrides out of public CSS.

Always verify extracted CSS sizes after build. If admin.css exceeds 100KB gzipped, audit for unused component imports. On a custom admin panel project, we reduced admin CSS from 180KB to 65KB simply by removing a global icon library import that was only needed in three components.

How do you debug and validate Laravel Vite custom bundle output?

Configuration mistakes in manualChunks silently produce broken builds or unexpected duplicates. Establish a validation routine before deploying. Never assume your chunking logic works correctly without inspection.

  1. Inspect the manifest: After npm run build, read public/build/manifest.json. Each entry lists its direct JS/CSS files plus imported chunks. Verify vendor chunks appear as imports, not duplicated inline.
  2. Check file sizes: Run ls -lhS public/build/assets/ sorted by size. Any single JS file over 250KB gzipped warrants investigation. Vendor chunks should be stable across deploys; app chunks should change frequently.
  3. Test cache headers: Confirm your web server sets long Cache-Control max-age for hashed assets. Vite's content hashing makes aggressive caching safe. Misconfigured headers negate all bundling benefits.
  4. Profile in browser: Use Chrome DevTools Network tab with "Disable cache" unchecked. Load a public page, then an admin page. Verify no admin chunks load on public pages and vice versa.
  5. Validate source maps: Ensure build.sourcemap is configured appropriately for your environment. Production should use 'hidden' to generate maps without exposing them publicly, enabling error tracking services to decode stack traces.

A frequent gotcha occurs when upgrading Laravel or Vite versions. The laravel-vite-plugin API occasionally changes between major versions. Always check the plugin changelog before upgrading. In my experience maintaining multiple client sites on shared infrastructure, pinning exact plugin versions in package.json prevents surprise build failures during routine updates.

Bundle Validation Workflow1. ManifestCheck imports & chunks2. File SizesFlag >250KB gzipped3. Cache HeadersVerify long max-age4. Browser TestNo cross-bundle leaks5. MapsHidden prodCommon Failure Points• manualChunks regex too broad → unexpected grouping• Plugin version mismatch after Laravel upgradeRun this checklist after every vite.config.js modification
Systematic validation prevents silent bundling errors in Laravel Vite custom asset configurations.

Implementing Sustainable Laravel Vite Config for Custom Asset Bundles

Effective Laravel Vite config for custom asset bundles is not a set-and-forget task. Treat it as living architecture that evolves with your application. Start with clear entry point separation, add manualChunks only when bundle analysis reveals actual waste, and validate every change against real production metrics. Document your chunking rationale in code comments so future maintainers understand why specific libraries are isolated.

If your current build ships unnecessary code to users or suffers from cache invalidation cascades, audit your Vite configuration today. Small adjustments often yield significant performance gains without application refactoring. For hands-on assistance optimizing your Laravel asset pipeline or broader Laravel architecture, reach out to discuss your project.

Frequently Asked Questions

It is the vite.config.js setup that defines multiple entry points, allowing you to compile separate CSS and JavaScript files for different application sections like admin panels or public storefronts.

Add an array of paths to the input option within laravel-vite-plugin in vite.config.js. Each path becomes a distinct bundle compiled to public/build/assets/, enabling isolated styling and scripting per application module without global bloat.

Separate bundles reduce initial page load by shipping only code required for the current route. On legal-tech portals I have built, splitting admin dashboard assets from public-facing content improved Core Web Vitals scores significantly by eliminating unused JavaScript payloads.

Yes. Use conditional logic in vite.config.js based on process.env.NODE_ENV or custom variables. This allows loading debug tools or analytics scripts only in specific environments while keeping production builds lean and secure for client-facing deployments.

Vite automatically extracts CSS imported within each JavaScript entry point into a corresponding .css file. When defining multiple inputs, each generates its own stylesheet, preventing style leakage between admin and public interfaces when using scoped component architectures.

Versioning happens automatically during npm run build via manifest.json. Reference assets using @vite() directive with the exact entry name defined in config. Laravel resolves the hashed filename at runtime, ensuring browsers always fetch updated assets after deployment.

Verify the entry path in vite.config.js matches exactly what you pass to @vite(). Check public/build/manifest.json exists after building. Ensure PHP-FPM opcache is invalidated post-deploy; stale manifests are a frequent cause on production servers I maintain.

Migration requires replacing mix() calls with @vite() and restructuring webpack.mix.js entries into vite.config.js input arrays. Unlike Mix, Vite uses native ES modules during development. Test thoroughly as import resolution differs, especially for jQuery-dependent legacy code common in older Nepal business sites.

Import Tailwind directives in each bundle's dedicated CSS entry file rather than a shared global stylesheet. Configure tailwind.config.js content paths to scan relevant directories. This ensures purged output remains minimal per bundle, critical for performance on content-heavy legal information sites.

Yes. Configure manualChunks in rollupOptions.output to extract common libraries like Vue or Alpine into a shared vendor chunk. This prevents duplicating framework code across multiple entry points while maintaining route-specific isolation for business logic and styling.

Initial setup adds two to four hours for typical multi-section applications. For Nepal-based clients budgeting around NPR 15,000–25,000 (~USD 110–185) for frontend architecture, this investment pays off through faster builds, better caching, and reduced bandwidth costs long-term.

Exposing source maps in production reveals application structure. Ensure build.sourcemap is false in vite.config.js for production. Also verify .env variables referenced in frontend code are intentionally public; Vite exposes only VITE_-prefixed vars, but misconfiguration can leak sensitive API keys.

Enable code splitting with dynamic imports inside entry files to defer non-critical modules. Configure compression plugins in Vite for Brotli/Gzip output. On projects like Court Marriage In Nepal, serving pre-compressed assets reduced transfer sizes by 60%, noticeably improving load times on mobile networks.

Yes. Define a dedicated Livewire entry point importing Alpine plugins and custom components separately from main app bundles. This prevents Livewire's JavaScript from loading on static pages. Register the bundle conditionally in Blade layouts where Livewire components actually render.

Node.js 22 LTS or 20 LTS is required. Older versions lack native fetch and modern ES module support that Vite 6+ expects. Always pin your Node version in .nvmrc to match production CI runners; mismatched versions cause subtle build failures I encounter regularly during GitLab CI deployments.

Share this article

Quick Contact Options
Choose how you want to connect me: