Inertia.js Roda (Lite)
A standalone Roda adapter for seamlessly integrating Inertia.js without views or other plugins dependency.
forked from https://github.com/holamendi/inertia-roda
Installation
Add this line to your application's Gemfile:
gem "roda-inertia-lite"Then run:
bundle installUsage
1. Register the plugin
require "roda"
class App < Roda
plugin :inertia, version: -> { "v1" }
endThe version option is required. It can be a string or a callable (e.g., a lambda reading from an environment variable):
# Static version
plugin :inertia, version: "abc123"
# Dynamic version (re-read on every request)
plugin :inertia, version: { ENV.fetch("ASSET_VERSION", "v1") }2. Render Inertia responses
Use inertia in your routes to return page data as JSON (for Inertia requests) or as a full HTML document (for initial page loads):
class App < Roda
plugin :inertia, version: -> { "v1" }
route do |r|
r.get "users" do
inertia "Users/Index", { users: User.all }
end
end
endFor Inertia requests (headers include X-Inertia: true):
- Sets response header
X-Inertia: true - Sets response header
Vary: X-Inertia - Returns a JSON page object:
{
"component": "Users/Index",
"props": { "users": [{ "id": 1, "name": "Alice" }] },
"url": "/users",
"version": "v1"
}For non-Inertia requests (initial page load):
- Returns a full HTML document with the JSON page data embedded in a
<div id="app" data-page='...'>tag - The HTML structure is built from the configured layout (see Custom Layout)
3. Handle redirects
Use inertia_redirect for both Inertia and non-Inertia requests:
class App < Roda
plugin :inertia, version: -> { "v1" }
route do |r|
r.post "users" do
user = User.create(r.params["user"])
inertia_redirect "/users/#{user.id}"
end
end
endFor Inertia requests:
- Internal paths (same host): redirects with 302 (GET) or 303 (non-GET)
- External URLs (different host): returns 409 Conflict with an
X-Inertia-Locationheader, telling the client to perform a full page visit
For non-Inertia requests:
- Always performs a standard redirect (default 302, or specify a custom status)
Custom status:
inertia_redirect "/dashboard", status: 3014. Check if a request is an Inertia request
Use inertia_request? to conditionally handle Inertia vs. non-Inertia requests:
route do |r|
r.get "dashboard" do
if inertia_request?
inertia "Dashboard", { stats: calculate_stats }
else
# Handle non-Inertia request differently
end
end
endConfiguration
Custom Layout
You can customize the HTML document structure string using the layout option:
plugin :inertia,
version: -> { "v1" },
layout: {
head: "<title>My App</title>",
body: "<footer>© 2024 My App</footer>"
}This produces:
<html><head><meta charset='utf-8' /><meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'><title>My App</title></head><body><div id='app' data-page='{...}'></div><footer>© 2024 My App</footer></body></html>How It Works
Version Conflicts (409 Responses)
When the client's asset version doesn't match the server's version (detected via the X-Inertia-Version header), the plugin returns a 409 Conflict response with an X-Inertia-Location header. This tells the Inertia client to perform a full page reload to fetch fresh assets.
Client Request:
X-Inertia: true
X-Inertia-Version: v0
Server Response:
Status: 409 Conflict
X-Inertia-Location: https://example.com/page
Request Flow
+------------------+
| Inertia Request?|
+--------+---------+
|
+--------------+--------------+
| |
v v
+-------+-------+ +--------+--------+
| X-Inertia: true| | Initial Load |
+-------+-------+ +--------+--------+
| |
v v
+---------+---------+ +---------+---------+
| Version matches? | | Return full HTML |
+---------+---------+ | document with |
| | embedded JSON |
+---------+---------+ +-------------------+
| Yes | No |
v v
+---+---+ +---+---+ +------------------+
| Return| | 409 | | Redirect: |
| JSON | |Conflict| | 302/303 |
+---+---+ +---+---+ +------------------+
External? -> 409
License
MIT