Skip to content

Quick start

This guide creates a working Hono + Vue 3 project from scratch. Adapt the imports for React or another router as needed.

Shortcutinertia init my-app generates all of this automatically. See CLI.


Minimal structure

my-app/
├── deno.json
├── package.json
├── vite.config.ts
├── server.ts
└── src/
    ├── main.ts
    └── pages/
        └── Home.vue

1. server.ts

ts
import { Hono } from "hono"
import { createInertia, pageToDiv, readViteManifest } from "deno-inertia"
import { toWebRequest } from "deno-inertia/hono"

const IS_PROD  = Deno.env.get("PROD_MODE") === "1"
const PORT     = Number(Deno.env.get("PORT") ?? 3000)
const VITE_URL = Deno.env.get("VITE_URL") ?? "http://localhost:5173"

// Production: read the manifest generated by vite build
const manifest = IS_PROD
  ? await readViteManifest("dist/.vite/manifest.json")
  : null

const inertia = createInertia({
  version: "1.0.0",

  // Assets: Vite in dev, manifest in prod
  ...(IS_PROD && manifest
    ? { prod: { manifest, entry: "src/main.ts" } }
    : { vite: { url: VITE_URL, entry: "/src/main.ts" } }),

  // Full HTML template
  template: (page, assets) => `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My App</title>
  ${assets}
</head>
<body>
  ${pageToDiv(page)}
</body>
</html>`,
})

const app = new Hono()

// Main route — renders the "Home" component with props
app.get("/", async (c) =>
  inertia.render(toWebRequest(c), "Home", {
    message: "Hello from Hono + Vue",
  }),
)

Deno.serve({ port: PORT }, app.fetch)
console.log(`Server running at http://localhost:${PORT}`)

2. src/main.ts (Vue)

ts
import { createApp, h } from "vue"
import { createInertiaApp } from "@inertiajs/vue3"

createInertiaApp({
  resolve: (name) => {
    const pages = import.meta.glob("./pages/**/*.vue", { eager: true })
    const page  = pages[`./pages/${name}.vue`]
    if (!page) throw new Error(`Page not found: "${name}"`)
    return page as object
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
})

3. src/pages/Home.vue

vue
<script setup lang="ts">
defineProps<{ message: string }>()
</script>

<template>
  <h1>{{ message }}</h1>
</template>

4. deno.json

jsonc
{
  "imports": {
    "deno-inertia":      "jsr:@streemkit/inertia-deno",
    "deno-inertia/hono": "jsr:@streemkit/inertia-deno/hono",
    "hono":              "jsr:@hono/hono@^4"
  },
  "tasks": {
    "install": "npm install",
    "dev":     "deno run -A jsr:@streemkit/inertia-deno-cli dev",
    "build":   "deno run -A jsr:@streemkit/inertia-deno-cli build",
    "preview": "deno run -A jsr:@streemkit/inertia-deno-cli preview"
  },
  "compilerOptions": {
    "lib": ["deno.ns", "dom", "dom.iterable", "esnext"]
  }
}

5. Run the project

bash
npm install       # install Vite + Vue
deno task dev     # start Vite (:5173) + Deno (:3000)

Open http://localhost:3000 — you'll see the message from your server.


What happens

  1. The browser loads http://localhost:3000/
  2. Deno responds with full HTML (first visit) containing <div id="app" data-page="…">
  3. Vite injects @vite/client (HMR) and src/main.ts
  4. Vue mounts the Home component and hydrates the JSON props
  5. Subsequent navigations (<Link href="…">) send X-Inertia: true → Deno responds with JSON → Vue updates the page without a full reload

Next steps

  • Configuration — advanced createInertia() options
  • Props — lazy, deferred, merge props
  • Forms — validation + error bags
  • CLI — generate a full project with inertia init