Vite integration
deno-inertia handles two distinct modes: development (Vite dev server + HMR) and production (compiled assets from the Vite manifest).
import {
viteDevScripts,
viteProdAssets,
readViteManifest,
serveStaticAsset,
} from "deno-inertia"Dev vs prod architecture
Dev : Browser ←→ Deno (:3000)
Vite (:5173) ← HMR, transforms, hot reload
Prod : Browser ←→ Deno (:3000)
dist/ ← compiled, hashed, immutable assetsDevelopment mode
viteDevScripts(config)
Generates <script> tags for the Vite client (HMR) and the frontend entry point.
interface ViteDevConfig {
url?: string // Vite server URL, default: "http://localhost:5173"
entry: string // Entry path — e.g. "/src/main.ts"
react?: boolean // Inject React Refresh preamble (@react-refresh)
}// Vue
viteDevScripts({ entry: "/src/main.ts" })
// generates:
// <script type="module" src="http://localhost:5173/@vite/client"></script>
// <script type="module" src="http://localhost:5173/src/main.ts"></script>// React — react: true required
viteDevScripts({ entry: "/src/main.tsx", react: true })
// also generates:
// <script type="module">
// import RefreshRuntime from "http://localhost:5173/@react-refresh"
// RefreshRuntime.injectIntoGlobalHook(window)
// window.$RefreshReg$ = () => {}
// window.$RefreshSig$ = () => (type) => type
// window.__vite_plugin_react_preamble_installed__ = true
// </script>Why
react: true?@vitejs/plugin-reactrequires a "preamble" script to install React Fast Refresh. When Vite serves the HTML itself (withindex.html), it injects it automatically. Here Deno serves the HTML — it must be injected manually. Without this flag:"can't detect preamble. Something is wrong."
vite.config.ts — CORS required
The browser loads assets from Vite (:5173) but the page comes from Deno (:3000). CORS must be enabled in Vite:
export default defineConfig({
server: { cors: true },
// ...
})Production mode
readViteManifest(path)
Reads the manifest.json file generated by vite build. Call once at server startup.
const manifest = await readViteManifest("dist/.vite/manifest.json")viteProdAssets(entry, manifest, base?)
Generates <link rel="stylesheet">, <link rel="modulepreload"> and <script type="module"> tags from the Vite manifest.
const assets = viteProdAssets("src/main.ts", manifest)
// → <link rel="modulepreload" href="/assets/vendor-abc123.js">
// <link rel="stylesheet" href="/assets/style-def456.css">
// <script type="module" src="/assets/main-ghi789.js"></script>- Recursively resolves CSS from imported chunks (code splitting)
- Generates
modulepreloadfor imported JS chunks base— asset URL prefix, default/assets/
Loose entry fallback — If the exact entry key is not found in the manifest, viteProdAssets attempts to resolve it in order:
- Suffix match (e.g.
"main.ts"matches"src/app/main.ts") - First chunk with
isEntry: true - Throws if nothing matches
A console warning is logged when an approximate match is used (not an error, for better DX).
serveStaticAsset(request, distDir, base?)
Serves static files from the dist/ directory with Cache-Control: public, max-age=31536000, immutable.
// In the main handler
async function handler(request: Request): Promise<Response> {
const { pathname } = new URL(request.url)
if (pathname.startsWith("/assets/")) {
const res = await serveStaticAsset(request, "dist")
return res ?? new Response("Not Found", { status: 404 })
}
return router.handler(request)
}Returns null if the URL does not start with base.
Usage in InertiaConfig
Auto mode (recommended)
The entry shorthand in createInertia() handles dev/prod detection automatically — no boilerplate needed. See configuration.md.
const inertia = createInertia({
entry: "src/main.ts", // auto dev/prod via PROD_MODE
// entry: "src/main.tsx", react: true, // React
template: (page, assets) => `...`,
})Explicit mode
const IS_PROD = Deno.env.get("PROD_MODE") === "1"
const manifest = IS_PROD ? await readViteManifest("dist/.vite/manifest.json") : null
const inertia = createInertia({
...(IS_PROD && manifest
? { prod: { manifest, entry: "src/main.ts" } }
: { vite: { entry: "/src/main.ts" } }), // Vue
// : { vite: { entry: "/src/main.tsx", react: true } }), // React
// ...
})Optional plugin: backend-reload
examples/vite_plugin_backend_reload.ts is a Vite plugin that watches backend files (server.ts, lib) and sends a full-reload to the browser when they change. The Deno server already restarts thanks to --watch, this plugin reloads the browser afterwards.
// vite.config.ts
// import { backendReloadPlugin } from "./vite_plugin_backend_reload.ts"
export default defineConfig({
plugins: [
vue(),
// backendReloadPlugin(), // uncomment to enable
],
})Disabled by default — the double-reload can be disruptive when working only on the frontend.