image

Inertia.js Rails Adapter

Installation

Backend

Add the inertia_rails gem to your Gemfile.

gem 'inertia_rails'

Follow the complete Server-side setup in the official documentation.

Frontend

Follow the Client-side setup guide for detailed configuration steps.

Example Projects:

Reference these sample implementations:

Usage

Responses

Render Inertia responses is simple, just use the inertia renderer in your controller methods. The renderer accepts two arguments, the first is the name of the component you want to render from within your pages directory (without extension). The second argument is an options hash where you can provide props to your components. This options hash also allows you to pass view_data to your layout, but this is much less common.

def index
  render inertia: 'Event/Index', props: {
    events: Event.all,
  }
end

Rails Component and Instance Props

Starting in version 3.0, Inertia Rails allows you to provide your component name and props via common rails conventions.

class EventsController < ApplicationController
  use_inertia_instance_props

  def index
    @events = Event.all
  end

end

is the same as

class EventsController < ApplicationController
  def index
    render inertia: 'events/index', props: {
      events: Event.all
    }
  end
end

Instance Props and Default Render Notes

In order to use instance props, you must call use_inertia_instance_props on the controller (or a base controller it inherits from). If any props are provided manually, instance props are automatically disabled for that response. Instance props are only included if they are defined after the before filter is set from use_inertia_instance_props.

Automatic component name is also opt in, you must set the default_render config value to true. Otherwise, you can simply render inertia: true for the same behavior explicitly.

If the default component path doesn't match your convention, you can define a method to resolve it however you like via the component_path_resolver config value. The value of this should be callable and will receive the path and action and should return a string component path.

inertia_config(
  component_path_resolver: ->(path:, action:) do
    "Storefront/#{path.camelize}/#{action.camelize}"
  end
)

Layout

Inertia layouts use the rails layout convention and can be set or changed in the same way.

class EventsController < ApplicationController
  layout 'inertia_application'
end

Shared Data

If you have data that you want to be provided as a prop to every component (a common use-case is information about the authenticated user) you can use the inertia_share controller method.

class EventsController < ApplicationController
  # share synchronously
  inertia_share app_name: env['app.name']

  # share lazily, evaluated at render time
  inertia_share do
    if logged_in?
      {
        user: logged_in_user,
      }
    end
  end

  # share lazily alternate syntax
  inertia_share user_count: lambda { User.count }

end

Deep Merging Shared Data

By default, Inertia will shallow merge data defined in an action with the shared data. You might want a deep merge. Imagine using shared data to represent defaults you'll override sometimes.

class ApplicationController
  inertia_share do
    { basketball_data: { points: 50, rebounds: 100 } }
  end
end

Let's say we want a particular action to change only part of that data structure. The renderer accepts a deep_merge option:

class CrazyScorersController < ApplicationController
  def index
    render inertia: 'CrazyScorersComponent',
    props: { basketball_data: { points: 100 } },
    deep_merge: true
  end
end

# The renderer will send this to the frontend:
{
  basketball_data: {
    points: 100,
    rebounds: 100,
  }
}

Deep merging can be configured using the deep_merge_shared_data configuration option.

If deep merging is enabled, you can still opt-out within the action:

class CrazyScorersController < ApplicationController
  inertia_config(deep_merge_shared_data: true)

  inertia_share do
    {
      basketball_data: {
        points: 50,
        rebounds: 10,
      }
    }
  end

  def index
    render inertia: 'CrazyScorersComponent',
    props: { basketball_data: { points: 100 } },
    deep_merge: false
  end
end

# `deep_merge: false` overrides the default:
{
  basketball_data: {
    points: 100,
  }
}

Lazy Props

On the front end, Inertia supports the concept of "partial reloads" where only the props requested are returned by the server. Sometimes, you may want to use this flow to avoid processing a particularly slow prop on the intial load. In this case, you can use Lazy props. Lazy props aren't evaluated unless they're specifically requested by name in a partial reload.

  inertia_share some_data: InertiaRails.lazy(lambda { some_very_slow_method })

  # Using a Ruby block syntax
  inertia_share some_data: InertiaRails.lazy { some_very_slow_method }

Routing

If you don't need a controller to handle a static component, you can route directly to a component with the inertia route helper

inertia 'about' => 'AboutComponent'

SSR (experimental)

Enable SSR via the configuration options for ssr_enabled and ssr_url.

When using SSR, don't forget to add <%= inertia_ssr_head %> to the <head> of your layout (i.e. application.html.erb).

Configuration ⚙️

Inertia Rails can be configured globally or in a specific controller (and subclasses).

Global Configuration

If using global configuration, we recommend you place the code inside an initializer:

# config/initializers/inertia.rb

InertiaRails.configure do |config|
  # Example: force a full-reload if the deployed assets change.
  config.version = ViteRuby.digest
end

The default configuration can be found here.

Local Configuration

Use inertia_config in your controllers to override global settings:

class EventsController < ApplicationController
  inertia_config(
    version: "events-#{InertiaRails.configuration.version}",
    ssr_enabled: -> { action_name == "index" },
  )
end

Configuration Options

This allows Inertia to detect if the app running in the client is oudated, forcing a full page visit instead of an XHR visit on the next request.

See assets versioning.

Default: nil

deep_merge_shared_data

When enabled, props will be deep merged with shared data, combining hashes with the same keys instead of replacing them.

Default: false

default_render

Overrides Rails default rendering behavior to render using Inertia by default.

Default: false

ssr_enabled (experimental)

Whether to use a JavaScript server to pre-render your JavaScript pages, allowing your visitors to receive fully rendered HTML when they first visit your application.

Requires a JS server to be available at ssr_url. Example

Default: false

ssr_url (experimental)

The URL of the JS server that will pre-render the app using the specified component and props.

Default: "http://localhost:13714"

Testing

If you're using Rspec, Inertia Rails comes with some nice test helpers to make things simple.

To use these helpers, just add the following require statement to your spec/rails_helper.rb

require 'inertia_rails/rspec'

And in any test you want to use the inertia helpers, add the inertia flag to the describe block

RSpec.describe EventController, type: :request do
  describe '#index', inertia: true do
    # ...
  end
end

Assertions

RSpec.describe EventController, type: :request do
  describe '#index', inertia: true do

    # check the component
    expect_inertia.to render_component 'Event/Index'

    # access the component name
    expect(inertia.component).to eq 'TestComponent'

    # props (including shared props)
    expect_inertia.to have_exact_props({name: 'Brandon', sport: 'hockey'})
    expect_inertia.to include_props({sport: 'hockey'})

    # access props
    expect(inertia.props[:name]).to eq 'Brandon'

    # view data
    expect_inertia.to have_exact_view_data({name: 'Brian', sport: 'basketball'})
    expect_inertia.to include_view_data({sport: 'basketball'})

    # access view data 
    expect(inertia.view_data[:name]).to eq 'Brian'

  end
end

Maintained and sponsored by the team at bellaWatt

bellaWatt Logo