The Basics
Asset bundling
Introduction
JCC Express MVC projects use Vite to bundle CSS and JavaScript from resources/ into assets the browser can load. The Laravel Vite plugin (laravel-vite-plugin) lines up with Laravel-style Blade helpers: @vite, @viteReactRefresh, and the {{ assets("…") }} helper inside templates.
Blade templates load dev scripts from the Vite dev server when APP_ENV is not production and public/hot exists. Production builds resolve hashed filenames through public/build/manifest.json (or the paths your build writes—see below).
For Inertia, SSR, and the HTTP kernel’s inertia() middleware, see Frontend. This page focuses on bundling, Blade tags, and dev vs production behavior for @vite and related directives.
NPM scripts (reference app)
Typical package.json scripts:
watch— Runsvite(dev server, HMR, writespublic/hot).dev— Runsbun --watch server.ts(simple backend reload; no Laravel-style watcher).serve— Run viabun artisanNode serve(recommended backend dev server; seeArtisan-Node.md).vite-build—vite buildplusvite build --ssrwhen you ship SSR (adjust to your app).build— TypeScript compile (tsc) for the server codebase; not a substitute forvite-build.
Start Vite before hitting pages that use @vite, or refresh after public/hot appears. The template layer reads public/hot on each request in development.
vite.config
Entries live in vite.config.mjs (or .ts). Example from this repo:
Add or change input when you create new entrypoints. Tailwind and React (or Vue) plugins sit beside laravel() as needed. Paths in @vite([...]) must match manifest keys after a production build (usually the same strings as in input).
public/hot and the dev server
In non-production, EngineHelpers reads public/hot on each render (first line is the Vite origin, e.g. http://127.0.0.1:5173). That value becomes viteHost for:
- Module
<script>tags pointing at your entries. - Optional link to the CSS entry when you pass more than one resource in
@vite(first entry is treated as CSS in dev in that case). - Injection of
/@vite/clientfor HMR.
If hot is missing, tags may point at an empty base — run npm run watch / bun run watch. After Vite creates public/hot, refresh the page (or use bun artisanNode serve, which watches public/hot and reloads the backend when Vite starts).
Blade: @vite
Syntax (comma-separated paths inside [ ]):
You may use leading slashes if you prefer (see resources/views/welcome.blade.html):
Development (APP_ENV not production): emits script (and related) tags that load from viteHost, and when there are multiple listed resources, adds a stylesheet link for the first and the Vite client script.
Production: reads build/manifest.json via getPublicFile (resolved under public/ or build/public/ depending on environment—see getPublicFile in engineHelper.ts). Each entry resolves to hashed files under /build/…. Linked CSS from the manifest is injected when present. If the manifest is missing, the engine logs a hint to run the Vite build.
More detail on directive parsing: JCC-Blade.md.
Blade: @viteReactRefresh
For React + Vite HMR, add @viteReactRefresh in <head> (non-production only injects the React refresh preamble):
Directive name in templates is @viteReactRefresh (matches expressions.ts).
{{ assets("dot.path") }}
Inside {{ }}, assets("something") is rewritten to a path: dots become slashes; if the first segment is js, the suffix .js is used, otherwise .css. Example: {{ assets("build.app") }} → path used as build/app.css for the lookup chain.
Prefer @vite for entrypoints so dev and production stay aligned with the manifest. Use assets() when you have a stable convention for built file names and the engine’s helper matches your layout.
Static files
Files in public/ are served as static assets (see Middleware.loadStaticFiles and Getting-Started/Directory-structure.md). Built Vite output normally lands under public/build/ after vite build, which is where the manifest and hashed assets expect to live for the Blade helpers.
Tailwind and sources
With @tailwindcss/vite, CSS often starts from resources/css/app.css using @import "tailwindcss" and @source paths so classes in .blade.html and resources/js are scanned. Tune @source in CSS when you add new template roots.
Troubleshooting
- Blank scripts or wrong host — Ensure
bun run watch/npm run watchis running andpublic/hotexists. - Production 500 or “Asset not found” — Run
vite-build(or your project’s equivalent) somanifest.jsonand/build/*files exist; confirmAPP_ENV=productionmatches howgetPublicFileresolves the manifest path. - Wrong entry — Manifest keys must match the strings you pass to
@vite(usually the same asinputinvite.config).