Templates

Configure the root template for Inertia applications.

See also

Official Inertia.js docs: Server-Side Setup

Root Template

The root template is the HTML shell for your Inertia application. It’s rendered on initial page loads with page data embedded.

templates/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/static/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title inertia>react-inertia</title>
    {{ vite_hmr() }}
    {{ vite('resources/main.tsx') }}
  </head>
  <body>
    <div id="app" data-page='{{ inertia | safe }}'></div>
  </body>
</html>

Key features:

  • <title inertia> - Enables dynamic title updates via Inertia’s <Head> component

  • {{ inertia | safe }} - JSON-encoded page props (use | safe to prevent escaping)

  • {{ vite_hmr() }} and {{ vite() }} - Vite asset injection

Template Helpers

These helpers are automatically available in your templates:

Helper

Description

{{ vite('path') }}

Include Vite assets (JS, CSS)

{{ vite_hmr() }}

HMR client script (dev mode)

{{ inertia | safe }}

JSON-encoded page props for data-page

{{ csrf_input }}

Hidden CSRF input field

{{ csrf_token }}

Raw CSRF token value

The inertia Helper

The {{ inertia | safe }} helper outputs JSON for the data-page attribute:

{
  "component": "Dashboard",
  "props": {"user": {"name": "Alice"}},
  "url": "/dashboard",
  "version": "abc123"
}

Important

Always use | safe with the inertia helper to prevent HTML escaping:

<div id="app" data-page='{{ inertia | safe }}'></div>

Without | safe, special characters in props will be escaped, breaking JSON parsing.

Dynamic Titles

Add the inertia attribute to <title> for dynamic title support:

<title inertia>Default Title</title>

Then update titles from your components using Inertia’s <Head> component:

import { Head } from '@inertiajs/react';

function Dashboard() {
  return (
    <>
      <Head title="Dashboard - My App" />
      {/* page content */}
    </>
  );
}

SPA Mode (No Templates)

In SPA mode, you don’t need Jinja templates. The index.html from your Vite project is used directly:

ViteConfig(
    mode="hybrid",  # Auto-detected with inertia=True
    inertia=InertiaConfig(spa_mode=True),
)

Your index.html (in resource_dir):

<!DOCTYPE html>
<html>
<head>
    <script type="module" src="/resources/main.ts"></script>
</head>
<body>
    <div id="app"></div>
</body>
</html>

Litestar-Vite automatically injects data-page and CSRF token.

Customizing the App Selector

Change the root element selector:

InertiaConfig(
    app_selector="#root",  # Default: "#app"
)
<div id="root" data-page='{{ inertia | safe }}'></div>

Multiple Templates

Use different templates for different sections:

@get("/admin/dashboard", component="Admin/Dashboard")
async def admin_dashboard() -> InertiaResponse:
    return InertiaResponse(
        content={...},
        template_name="admin.html",  # Override default
    )

Template Organization

Recommended structure:

templates/
├── index.html       # Main Inertia template
├── admin.html       # Admin section template
└── partials/
    └── head.html    # Shared head content

Using template inheritance:

templates/base.html
<!DOCTYPE html>
<html>
<head>
    {% block head %}
    {{ vite('resources/main.ts') }}
    {{ vite_hmr() }}
    {% endblock %}
</head>
<body>
    {% block body %}{% endblock %}
</body>
</html>
templates/index.html
{% extends "base.html" %}

{% block body %}
<div id="app" data-page='{{ inertia | safe }}'></div>
{% endblock %}

See Also