Skip to content

Quick Start: Jinja2 Templates & Islands

In Template mode (mode="template"), Litestar renders server-side Jinja2 templates while VitePlugin registers template callables (vite_hmr(), vite(), vite_fragment(), vite_static()) and mounts <litestar-island> web components or HTMX ComponentResponse fragments.


Install Dependencies

Install Litestar Vite with the jinja extra:

uv add "litestar-vite[jinja]"
pip install "litestar-vite[jinja]"
pdm add "litestar-vite[jinja]"
poetry add "litestar-vite[jinja]"

Install litestar-vite-plugin in your frontend workspace:

npm install -D litestar-vite-plugin
pnpm add -D litestar-vite-plugin
bun add -d litestar-vite-plugin

Configure Litestar

Configure TemplateConfig(engine=JinjaTemplateEngine) alongside VitePlugin(config=ViteConfig(mode="template")). Route handlers can return standard Template responses for full pages or ComponentResponse for partial HTMX swaps:

docs/examples/getting_started/template_app.py
"""Jinja2 server-rendered templates with Vite assets and interactive islands."""

from __future__ import annotations

from pathlib import Path

from litestar import Litestar, get
from litestar.plugins.jinja import JinjaTemplateEngine
from litestar.response import Template
from litestar.template.config import TemplateConfig

from litestar_vite import ComponentResponse, PathConfig, RuntimeConfig, ViteConfig, VitePlugin


@get("/", sync_to_thread=False)
def home_page() -> Template:
    """Render a server-driven Jinja2 template with Vite asset tags."""
    return Template(template_name="home.html.j2", context={"title": "Orders Overview", "initial_count": 42})


@get("/fragments/metrics-card", sync_to_thread=False)
def metrics_fragment() -> ComponentResponse:
    """Return a pre-rendered UI component fragment for HTMX swap targets."""
    return ComponentResponse(component="components/MetricsCard.tsx", props={"activeOrders": 42, "latencyMs": 11.4})


def create_app(templates_dir: Path | None = None) -> Litestar:
    """Create a Litestar application in ``template`` mode with Jinja2."""
    resolved_dir = templates_dir or Path()
    vite_config = ViteConfig(
        mode="template",
        dev_mode=True,
        paths=PathConfig(root=Path(), resource_dir=Path("resources"), bundle_dir=Path("public/build")),
        runtime=RuntimeConfig(start_dev_server=False),
    )
    return Litestar(
        route_handlers=[home_page, metrics_fragment],
        template_config=TemplateConfig(directory=resolved_dir, engine=JinjaTemplateEngine),
        plugins=[VitePlugin(config=vite_config)],
    )


def main() -> None:
    """Verify the template mode application and component fragment response."""
    app = create_app()
    plugin = app.plugins.get(VitePlugin)
    assert plugin.config.mode == "template"
    fragment = ComponentResponse(component="components/MetricsCard.tsx", props={"activeOrders": 42, "latencyMs": 11.4})
    assert fragment.component == "components/MetricsCard.tsx"
    assert fragment.props["activeOrders"] == 42


__all__ = ("create_app", "home_page", "main", "metrics_fragment")

Author the Jinja2 Template

Use {{ vite_hmr() }} and {{ vite('resources/main.ts') }} in <head>, and embed interactive islands with {{ vite_fragment('components/MetricsCard.tsx', props={'activeOrders': initial_count}, hydrate='visible') }}:

home.html.j2
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>{{ title }}</title>
    {{ vite_hmr() }}
    {{ vite('resources/main.ts') }}
  </head>
  <body>
    <main>
      <h1>{{ title }}</h1>
      {{ vite_fragment(
          'components/MetricsCard.tsx',
          props={'activeOrders': initial_count, 'latencyMs': 11.4},
          hydrate='visible'
      ) }}
      <button
        hx-get="/fragments/metrics-card"
        hx-target="#live-metrics"
        hx-swap="innerHTML">
        Refresh Metrics
      </button>
      <div id="live-metrics"></div>
    </main>
  </body>
</html>

Register Island Hydration

Register the <litestar-island> custom element in your client entrypoint so islands hydrate according to their data-hydrate strategy (load, idle, visible, or media):

resources/main.ts
import { hydrateIslands } from "litestar-vite-plugin/fragments/client";
import { hydrateReact } from "litestar-vite-plugin/fragments/react";

hydrateIslands({
  "islands/RevenueChart": {
    load: () => import("./islands/RevenueChart"),
    hydrate: hydrateReact,
  },
});

See Islands Architecture & HTMX Integration for the complete lifecycle of vite_fragment(), <litestar-island>, and ComponentResponse.