Project

inertia_jb

0.0
The project is in a healthy, maintained state
InertiaJb lets you declare Inertia.js props for your Rails frontend components inside `.html.inertia` view templates using plain Ruby Hashes, powered by the jb renderer. Built on top of inertia_rails.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 2.5
~> 13.0

Runtime

~> 0.8
 Project Readme

InertiaJb

InertiaJb lets you declare Inertia.js props for your Rails frontend components inside view templates, using plain Ruby Hashes powered by jb.

It is built on top of the inertia-rails gem.

A *.html.inertia template is just Ruby code whose last expression is a Hash. That Hash is handed straight to InertiaRails::Renderer, so every Inertia protocol feature works out of the box — partial reloads (at any nesting depth), optional / always / deferred / scroll props, prop merging, shared data, and global key transforms.

# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  # Shared props are merged in automatically.
  inertia_share user: -> { Current.user }

  def show
    @message = Message.find(params[:id])
  end
end
# app/views/messages/show.html.inertia
{
  message: {
    content: @message.content,
    author: {
      name: @message.author.name,
      email: @message.author.email,
      url: url_for(@message.author, format: :json)
    },
    comments: render(partial: "comments/comment", collection: @message.comments)
  }
}
// app/javascript/pages/messages/show.jsx
export default function Message({ user, message }) {
  return (
    <div>
      <p>Hi, {user.name}</p>
      <p>{message.content}</p>
      <a href={message.author.url}>
        {message.author.name} &lt;{message.author.email}&gt;
      </a>
      {message.comments.map((c) => <Comment key={c.id} comment={c} />)}
    </div>
  );
}

Why jb?

Inertia's server side is fundamentally about producing a Hash of props that inertia-rails then resolves and serializes. jb templates are plain Ruby Hashes, so there is zero impedance mismatch: no DSL to learn, no intermediate representation, and the full power of Ruby for building collections and conditionals.

Coming from props_template? That gem streams JSON strings, which don't map cleanly onto Inertia's Hash-based resolver. jb's plain-Hash output is the natural fit, which is why this gem is built on it.

Installation

Add the gem to your Gemfile:

gem "inertia_jb"

Then, in your inertia-rails initializer, disable default_render so that a normal implicit render falls through to your .html.inertia template:

# config/initializers/inertia_rails.rb
InertiaRails.configure do |config|
  config.default_render = false
end

You do not need to call use_inertia_instance_props.

Templates and partials

  • Page templates live at app/views/<controller>/<action>.html.inertia and must return a Hash (your Inertia props).
  • Partials are ordinary jb partials named _name.html.jb. They return a Hash, and compose naturally:
# app/views/messages/show.html.inertia
{
  author:   render(partial: "authors/author", object: @message.author),
  comments: render(partial: "comments/comment", collection: @message.comments)
}
# app/views/authors/_author.html.jb
{ id: author.id, name: author.name }

render(partial:, collection:) returns an array of Hashes (thanks to jb), which you embed directly. Don't name partials .html.inertia — that extension triggers the Inertia response wrapper and is only for top-level page templates.

Inertia prop types

Because props are just a Hash, Inertia's special prop types are plain values you drop in. Inside a .html.inertia template you can use the short helpers (optional, always, defer, scroll, merge, deep_merge) or the full InertiaRails.* methods.

{
  id:    @post.id,
  title: @post.title,

  # Only fetched when explicitly requested in a partial reload.
  stats: optional { @post.expensive_stats },

  # Always fetched, even if not requested in a partial reload.
  settings: always { current_settings },

  # Excluded from the initial load; fetched in a follow-up request.
  comments: defer(group: :comments) {
    render(partial: "comments/comment", collection: @post.comments)
  },

  # Infinite scrolling (accepts a paginator or explicit metadata).
  feed: scroll(@pagy) {
    render(partial: "feed/item", collection: @items)
  }
}

See the inertia-rails docs for partial reloads, deferred props, and infinite scroll.

Caching

Use plain Rails caching — you're caching Ruby Hashes:

@posts.map do |post|
  Rails.cache.fetch(post) { render(partial: "posts/post", object: post) }
end

camelCase keys

If you configure inertia-rails' prop_transformer to camelize keys, it applies at every nesting depth — because your props reach inertia-rails as a real Hash:

# config/initializers/inertia_rails.rb
InertiaRails.configure do |config|
  config.default_render = false
  config.prop_transformer = ->(props:) { props.deep_transform_keys { |k| k.to_s.camelize(:lower) } }
end

Alternatively, just write camelCase keys directly in your templates.

Notes

  • Inertia props must be an object, so page templates should return a Hash (not a top-level Array).
  • Don't install this alongside inertia-builder; both register an :inertia template handler.

Development

bundle install
bundle exec rake test

License

MIT. See LICENSE.