Skip to content
Inertia

Your first page

This walkthrough assumes Installation is done: provider, middleware, root view, and a Vite client that resolves Pages/*.

router.get("/") {
Inertia.render(
"Pages/Welcome",
mapOf(
"title" to "Hello",
"message" to "First Inertia page",
),
)
}

Inertia.render requires a current request (set by HandleInertiaRequests) unless you pass the Request explicitly. See Rendering.

Browser navigates to / with no X-Inertia header.

  1. Middleware sets the request on InertiaManager, merges shared props, checks version (none on first document load).
  2. Your route returns an Inertia response.
  3. Manager builds an InertiaPage and renders the root Crema view (inertia.root_view) with:
    • pageAttr — HTML-escaped JSON of the page object
    • vite — script/link tags for the client entry
  4. Response is text/html.

Inspect #app:

<div
id="app"
data-page="{&quot;component&quot;:&quot;Pages/Welcome&quot;,&quot;props&quot;:{&quot;title&quot;:&quot;Hello&quot;,…},&quot;url&quot;:&quot;/&quot;,&quot;version&quot;:&quot;1&quot;}"
></div>

The Vue/React/Svelte entry hydrates that JSON into Pages/Welcome.

After the client is mounted, an Inertia link or router.visit sends:

GET / HTTP/1.1
X-Inertia: true
X-Inertia-Version: 1
Accept: text/html, application/xhtml+xml
X-Requested-With: XMLHttpRequest

(Exact client headers depend on the adapter; X-Inertia: true is required for JSON mode.)

Server behavior:

  1. Version header matches → continue.
  2. Route renders the same Inertia.render(…).
  3. Response body is JSON (not the Crema shell):
{
"component": "Pages/Welcome",
"props": { "title": "Hello", "message": "First Inertia page" },
"url": "/",
"version": "1"
}

Headers include X-Inertia: true, Vary: X-Inertia, and X-Inertia-Version when a version is configured.

If the client sends a stale X-Inertia-Version, middleware returns 409 so the adapter can force a full reload and pick up new assets. Configure inertia.version (or override version(request) on your middleware). See Asset versioning.

Flash messages, auth user, and ziggy-style URLs belong in middleware share(request) or Inertia.share(…) — not in every controller. See Shared data.

Match the server name. Vue example (js/Pages/Welcome.vue):

<script setup>
defineProps({ title: String, message: String });
</script>
<template>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
</template>