---
url: /guide/asset-versioning.md
---
# Asset Versioning
One common challenge when building single-page apps is refreshing site assets when they've been changed. Thankfully, Inertia makes this easy by optionally tracking the current version of your site assets. When an asset changes, Inertia will automatically make a full page visit instead of a XHR visit on the next request.
## Configuration
To enable automatic asset refreshing, you need to tell Inertia the current version of your assets using the `InertiaRails.configure` method and setting the `config.version` property. This can be any arbitrary string (letters, numbers, or a file hash), as long as it changes when your assets have been updated.
```ruby
InertiaRails.configure do |config|
config.version = ViteRuby.digest # or any other versioning method
end
# You can also use lazy evaluation
InertiaRails.configure do |config|
config.version = lambda { ViteRuby.digest }
end
```
## Cache Busting
Asset refreshing in Inertia works on the assumption that a hard page visit will trigger your assets to reload. However, Inertia doesn't actually do anything to force this. Typically this is done with some form of cache busting. For example, appending a version query parameter to the end of your asset URLs.
## Manual Refreshing
If you want to take asset refreshing into your own control, you can set the version to a fixed value. This disables Inertia's automatic asset versioning.
For example, if you want to notify users when a new version of your frontend is available, you can still expose the actual asset version to the frontend by including it as [shared data](/guide/shared-data).
```ruby
# config/initializers/inertia_rails.rb
InertiaRails.configure do |config|
# Disable automatic asset versioning
config.version = nil
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
inertia_share version: -> { ViteRuby.digest }
end
```
On the frontend, you can watch the `version` property and show a notification when a new version is detected.
---
---
url: /guide/authentication.md
---
# Authentication
One of the benefits of using Inertia is that you don't need a special authentication system such as OAuth to connect to your data provider (API). Also, since your data is provided via your controllers, and housed on the same domain as your JavaScript components, you don't have to worry about setting up CORS.
Rather, when using Inertia, you can simply use whatever authentication system you like, such as solutions based on Rails' built-in `has_secure_password` method, or gems like [Devise](https://github.com/heartcombo/devise), [Sorcery](https://github.com/Sorcery/sorcery), [Authentication Zero](https://github.com/lazaronixon/authentication-zero), etc.
---
---
url: /guide/authorization.md
---
# Authorization
When using Inertia, authorization is best handled server-side in your application's authorization policies. However, you may be wondering how to perform checks against your authorization policies from within your Inertia page components since you won't have access to your framework's server-side helpers.
The simplest approach to solving this problem is to pass the results of your authorization checks as props to your page components.
Here's an example of how you might do this in a Rails controller using the [Action Policy](https://github.com/palkan/action_policy) gem:
```ruby
class UsersController < ApplicationController
def index
render inertia: {
can: {
create_user: allowed_to?(:create, User)
},
users: User.all.map do |user|
user.as_json(
only: [:id, :first_name, :last_name, :email]
).merge(
can: {
edit_user: allowed_to?(:edit, user)
}
)
end
}
end
end
```
---
---
url: /awesome.md
---
# Awesome Inertia Rails
A curated list of community resources, tools, and content for Inertia Rails.
## Starter kits (alphabetical)
* [GrowthX Starter Kit](https://github.com/growthxai/starter) - Rails 8 + React 19 + Inertia.js + Shadcn/UI Starter Kit from GrowthX.ai
* [Inertia Rails Starter Kits](https://inertia-rails.dev/guide/starter-kits) — Official starter kits for React, Vue, and Svelte (TypeScript, shadcn/ui)
* [Kaze](https://github.com/gtkvn/kaze) — Rails authentication scaffolding (Hotwire/React/Vue)
* [Kickstart](https://github.com/alec-c4/kickstart) — Rails application starter kits, Inertia-based (React, Svelte, Vue) and classic (API, esbuild and importmaps)
* [Svelte starter template](https://github.com/georgekettle/rails_svelte) — Svelte starter template (Svelte, shadcn/ui)
## Packages (alphabetical)
### Inertia-specific
* [Alba::Inertia](https://github.com/skryukov/alba-inertia) — Seamless integration between Alba serializers and Inertia Rails
* [Inertia Modal](https://github.com/inertiaui/modal) — Open any route in a Modal or Slideover without having to change anything about your existing routes or controllers (React, Vue)
* [Inertia X](https://github.com/buhrmi/inertiax) — Svelte-only fork of Inertia with additional features (Svelte)
* [InertiaBuilder](https://github.com/rodrigotavio91/inertia-builder) — JBuilder-like DSL for declaring Inertia.js props
* [InertiaCable](https://github.com/cole-robertson/inertia-cable) — Real-time prop updates for Inertia.js Rails apps using ActionCable
* [InertiaI18n](https://github.com/alec-c4/inertia_i18n) — Translation management bridging Rails YAML and frontend i18next JSON
* [useInertiaForm](https://github.com/aviemet/useInertiaForm) — Direct replacement of Inertia's useForm hook with support for nested forms (React)
### General
* [JS From Routes](https://github.com/ElMassimo/js_from_routes) — Generate path helpers and API methods from your Rails routes
* [JsRoutes](https://github.com/railsware/js-routes) — Brings Rails named routes to JavaScript
* [Typelizer](https://github.com/skryukov/typelizer) — A TypeScript type generator for Ruby serializers
* [types\_from\_serializers](https://github.com/ElMassimo/types_from_serializers) — Generate TypeScript interfaces from your JSON serializers
## Articles (newest first)
* [Optimistic UI in Rails with optimism... and Inertia](https://evilmartians.com/chronicles/optimistic-ui-in-rails-with-optimism-and-inertia) — Deep dive by Svyatoslav Kryukov (2026)
* [Redprints CFP: an open source CFP management app built with Rails + Inertia.js](https://evilmartians.com/chronicles/redprints-cfp-open-source-cfp-management-app-build-with-rails-and-inertia-js) — Deep dive by Vladimir Dementyev (2025)
* [Simplicity, vanished?! Solving the mystery with Inertia.js + Rails](https://evilmartians.com/chronicles/simplicity-vanished-solving-the-mystery-with-inertia-js-and-rails) — Deep dive by Svyatoslav Kryukov (2025)
* [Building Filters with Inertia.js and Rails: A Clean Approach](https://pedro.switchdreams.com.br/inertiajs/2025/06/03/filters-with-inertia-and-rails/) — Guide by Pedro Duarte (2025)
* [How to Build a Twitter Clone with Rails 8 Inertia and React](https://robrace.dev/blog/build-a-twitter-clone-with-rails-inertia-and-react) — Tutorial by Rob Race (2025)
* [How to Handle Bundle Size in Inertia.js](https://pedro.switchdreams.com.br/inertiajs/2025/03/21/handle-bundle-size-inertiajs) — Guide by Pedro Duarte (2025)
* [How We Fell Out of Love with Next.js and Back in Love with Ruby on Rails & Inertia.js](https://hardcover.app/blog/part-1-how-we-fell-out-of-love-with-next-js-and-back-in-love-with-ruby-on-rails-inertia-js) — Deep dive by Adam Fortuna (2025)
* [Building an InertiaJS app with Rails](https://avohq.io/blog/inertia-js-with-rails) — Tutorial by Exequiel Rozas (2025)
* [Inertial Rails project setup to use code generated from v0 (ShadcnUI, TailwindCSS4, React, TypeScript) and deploy with Kamal](https://tuyenhx.com/blog/inertia-rails-shadcn-typescript-ssr-en/) — Guide by Tom Ho (2025)
* [Keeping Rails cool: the modern frontend toolkit](https://evilmartians.com/chronicles/keeping-rails-cool-the-modern-frontend-toolkit) — Deep dive by Irina Nazarova (2024)
* [Inertia.js in Rails: a new era of effortless integration](https://evilmartians.com/chronicles/inertiajs-in-rails-a-new-era-of-effortless-integration) — Deep dive by Svyatoslav Kryukov (2024)
## Videos and talks (newest first)
* [Inertia Rails Workshop](https://www.youtube.com/watch?v=XBZcLD5xcPY) — SF Ruby Conference 2025 workshop by Brandon Shar, Svyatoslav Kryukov, and Brian Knoles ([demo app](https://github.com/inertia-rails/inertia-workshop-demo))
* [Optimistic Drawer UI with Inertia.js and Rails](https://www.youtube.com/watch?v=WOd3uNlW37I) — by Brian Knoles (2025)
* [Optimistic UI Updates with Inertia.js and Rails](https://www.youtube.com/watch?v=B8D195yQX04) — by Brian Knoles (2025)
* [The Best of Both Worlds: InertiaJS + React + Rails](https://www.youtube.com/watch?v=MHqITeJGci0) — by Colleen Schnettler (2025)
* [Tropical on Rails 2025: Defying Front-End Inertia](https://www.youtube.com/watch?v=uLFItMoF_wA) — Conference talk by Svyatoslav Kryukov (2025)
* [Ken Greeff's YouTube channel](https://www.youtube.com/@kengreeff/search?query=inertia) — Fresh Inertia Rails content (2025)
* [InertiaJS on Rails](https://www.youtube.com/watch?v=03EjkPaCHEI\&list=PLRxuhjCzzcWj4MUjDCC9TCP_ZfcRL0I1s) — YouTube course by Brandon Shar (2021)
* [RailsConf 2021: Inertia.js on Rails Lightning Talk](https://www.youtube.com/watch?v=-JT1RF-IhKs) — Conference talk by Brandon Shar (2021)
## Example applications (alphabetical)
### Open source
* [Code Basics](https://code-basics.com) — Free online programming courses. **Code available on [GitHub](https://github.com/hexlet-basics/hexlet-basics)**
* [Redprints CFP](https://github.com/evilmartians/redprints-cfp) — Open source CFP management app built with Rails and Inertia.js
### Demo
* [Ruby on Rails/Vue](https://github.com/ledermann/pingcrm) by Georg Ledermann
* [Ruby on Rails/Vue SSR/Vite](https://github.com/ElMassimo/pingcrm-vite) by Máximo Mussini
### Production
* [Calm Companies](https://calmcompanies.club) — Get weekly emails when companies with a great work culture are hiring
* [Clipflow](https://www.clipflow.co) — Project management for content creators
* [Crevio](https://crevio.co) — All-In-One creator store
* [Hardcover](https://hardcover.app) — A social network for book lovers
* [OG Pilot](https://ogpilot.com) — A dynamic Open Graph image generator tool
* [Switch Kanban](https://switchkanban.com.br) — Project management tool for software houses
## Other (alphabetical)
* [Inertia Rails Skills](https://github.com/cole-robertson/inertia-rails-skills) — Agent skills and best practices for AI coding assistants working with Inertia Rails
* [Inertia.js devtools](https://chromewebstore.google.com/detail/inertiajs-devtools/golilfffgehhabacoaoilfgjelagablo?hl=en) — Inertia.js page JSON in devtools panel
## Community
* [X.com](https://x.com/inertiajs) — Official X account
* [Discord](https://discord.gg/inertiajs) — Official Discord server
* [Reddit](https://www.reddit.com/r/inertiajs) — Inertia.js subreddit
*Please share your projects and resources with us!*
---
---
url: /guide/cached-props.md
---
# Cached Props
@available\_since rails=3.21.0
Cached props use your server-side cache store to avoid recomputing expensive data on every request. When the cache is warm, the block is never evaluated — Inertia serves the pre-serialized JSON directly.
> \[!NOTE]
> To understand when to use cached props vs once props vs HTTP caching, see the [Caching](/guide/caching) guide.
## Creating Cached Props
To create a cached prop, use the `InertiaRails.cache` method. This method requires a cache key and a block that returns the prop data.
```ruby
class DashboardController < ApplicationController
def index
render inertia: {
stats: InertiaRails.cache('dashboard_stats', expires_in: 1.hour) { Stats.compute },
}
end
end
```
On the first request, the block is evaluated, serialized to JSON, and written to the cache. Subsequent requests serve the cached JSON without evaluating the block.
## Cache Keys
Cache keys determine when cached data is invalidated. Inertia supports several key formats.
### String Keys
The simplest form — a static string:
```ruby
InertiaRails.cache('sidebar_nav') { NavigationItem.tree }
```
### Active Record Objects
Pass an Active Record object to derive the key from `cache_key_with_version`. The cache is automatically invalidated when the record is updated:
```ruby
InertiaRails.cache(@post) { PostSerializer.render(@post) }
# Cache key: "inertia_rails/posts/1-20260410120000"
```
### Array Keys
Pass an array to build a composite key:
```ruby
InertiaRails.cache(['stats', current_user.id]) { Stats.for(current_user) }
# Cache key: "inertia_rails/stats/42"
```
## Cache Options
You can pass `expires_in` and `race_condition_ttl` options to control cache behavior:
```ruby
InertiaRails.cache('stats', expires_in: 1.hour) { Stats.compute }
InertiaRails.cache('stats', expires_in: 1.hour, race_condition_ttl: 10.seconds) { Stats.compute }
```
## Combining with Other Prop Types
The `cache` option can be passed to [deferred](/guide/deferred-props) and [optional](/guide/partial-reloads#lazy-data-evaluation) props:
```ruby
class DashboardController < ApplicationController
def index
render inertia: {
# Deferred prop with caching
feed: InertiaRails.defer(cache: { key: 'feed', expires_in: 5.minutes }, group: 'feed') { current_user.feed },
# Optional prop with caching
categories: InertiaRails.optional(cache: @team) { @team.categories },
}
end
end
```
The `cache` option accepts the same key formats as `InertiaRails.cache`: strings, Active Record objects, arrays, and hashes with options.
```ruby
InertiaRails.defer(cache: { key: 'feed', expires_in: 5.minutes }) { current_user.feed }
```
## Cache Store
By default, Inertia uses `Rails.cache`. You can configure a different store via the [`cache_store`](/guide/configuration#cache_store) option. All cached prop keys are automatically prefixed with `inertia_rails/` to avoid collisions.
For more information on configuring cache stores, cache key strategies, and expiration policies, see the [Rails low-level caching guide](https://guides.rubyonrails.org/caching_with_rails.html#low-level-caching-using-rails-cache).
---
---
url: /guide/caching.md
---
# Caching
Inertia Rails offers several complementary strategies for avoiding redundant work. Each solves a different problem, and you can combine them. If you're new to caching in Rails, start with the [Rails caching guide](https://guides.rubyonrails.org/caching_with_rails.html).
## Choosing a Strategy
| Strategy | What it saves | Where it lives | Best for |
| ------------------------------------ | --------------------------- | ----------------------- | ----------------------------------------- |
| [HTTP caching](#http-caching) | Entire response render | Browser / CDN | Pages tied to a single record's freshness |
| [Cached props](#prop-level-caching) | Block evaluation | Server-side cache store | Expensive queries or computations |
| [Once props](#once-props) | Bandwidth and serialization | Client memory | Large or rarely-changing shared data |
| [SSR caching](#ssr-response-caching) | SSR render request | Server-side cache store | Avoiding redundant Node.js SSR calls |
In short: **cached props skip computing**, **once props skip sending**, **HTTP caching skips rendering**, and **SSR caching skips the SSR request**.
These strategies are independent. A prop can be both cached on the server and marked as once so the client doesn't re-request it. HTTP caching can wrap an entire response that contains cached props. SSR caching can be layered on top of any combination.
### Why only `defer` and `optional` support the `cache` option
The `cache` option is available on [deferred](/guide/deferred-props) and [optional](/guide/partial-reloads#lazy-data-evaluation) props because these represent data that is loaded on demand — caching their result avoids re-evaluating expensive blocks on repeated requests.
Other prop types don't need it:
* **Once props** already skip evaluation when the client has the data. If the computation itself is expensive, use `InertiaRails.cache` directly in the block.
* **Always props** are meant for cheap, frequently-changing data (flash messages, auth state). If the data is expensive enough to cache, it probably shouldn't be `always`.
* **Merge and scroll props** describe how the client handles the data, not how the server computes it. If the underlying data is expensive, wrap it with `InertiaRails.cache` and pass the result.
## HTTP Caching
Rails provides built-in HTTP caching via `stale?` and `fresh_when`. These work with Inertia responses, but require one adjustment: because the same URL returns HTML on the initial page load and JSON on subsequent Inertia visits, ETags must account for the request type.
### Differentiating ETags
Use the `etag` method in your controller to include `request.inertia?` in the ETag calculation:
```ruby
class ApplicationController < ActionController::Base
etag { request.inertia? }
end
```
This ensures that HTML and JSON responses for the same URL produce different ETags, preventing the browser from serving a stale cached response in the wrong format.
### Using `stale?`
With the ETag differentiation in place, use `stale?` as you normally would in Rails:
```ruby
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
if stale?(@post)
render inertia: { post: @post.as_json }
end
end
end
```
When the post hasn't changed, Rails returns a `304 Not Modified` response and skips rendering entirely.
### Using `fresh_when`
For simpler cases where you don't need conditional logic:
```ruby
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
fresh_when(@post)
render inertia: { post: @post.as_json }
end
end
```
## Prop-Level Caching
Prop-level caching stores computed values in your Rails cache store, skipping expensive block evaluation on cache hits. See the [Cached props](/guide/cached-props) guide for full details.
```ruby
class DashboardController < ApplicationController
def index
render inertia: {
stats: InertiaRails.cache('dashboard_stats', expires_in: 1.hour) { Stats.compute },
feed: InertiaRails.defer(cache: { key: 'feed', expires_in: 5.minutes }) { current_user.feed },
}
end
end
```
## Once Props
Once props are remembered by the client and excluded from subsequent responses. This saves bandwidth for data that rarely changes, such as shared navigation or role lists. See the [Once props](/guide/once-props) guide for full details.
```ruby
class ApplicationController < ActionController::Base
inertia_share countries: InertiaRails.once { Country.all }
end
```
## SSR Response Caching
When SSR is enabled, each page load sends a request to the Node.js server. SSR caching stores these responses so identical page data is only rendered once. See [SSR response caching](/guide/server-side-rendering#ssr-response-caching) for full details.
```ruby
InertiaRails.configure do |config|
config.ssr_cache = true
end
```
---
---
url: /guide/client-side-setup.md
---
# Client-Side Setup
Once you have your [server-side framework configured](/guide/server-side-setup), you then need to setup your client-side framework. Inertia currently provides support for React, Vue, and Svelte.
## Prerequisites
> \[!NOTE]
> You can skip this step if you have already executed the [Rails generator](/guide/server-side-setup#rails-generator).
Inertia requires your client-side framework and its corresponding Vite plugin to be installed and configured. You may skip this section if your application already has these set up.
:::tabs key:frameworks
\== Vue
```bash
npm install vue @vitejs/plugin-vue
```
\== React
```bash
npm install react react-dom @vitejs/plugin-react
```
\== Svelte
```bash
npm install svelte @sveltejs/vite-plugin-svelte
```
:::
Then, add the framework plugin to your `vite.config.js` file.
:::tabs key:frameworks
\== Vue
```js
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [RubyPlugin(), vue()],
})
```
\== React
```js
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [RubyPlugin(), react()],
})
```
\== Svelte
```js
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import { svelte } from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
plugins: [RubyPlugin(), svelte()],
})
```
:::
For more information on configuring these plugins, consult [Vite Rails documentation](https://vite-ruby.netlify.app).
## Installation
> \[!NOTE]
> You can skip this step if you have already executed the [Rails generator](/guide/server-side-setup#rails-generator).
### Install dependencies
@available\_since core=3.0.0
Install the Inertia client-side adapter and Vite plugin.
> \[!NOTE]
> The `@inertiajs/vite` plugin supports Vite 7 and Vite 8.
:::tabs key:frameworks
\== Vue
```bash
npm install @inertiajs/vue3 @inertiajs/vite
```
\== React
```bash
npm install @inertiajs/react @inertiajs/vite
```
\== Svelte
```bash
npm install @inertiajs/svelte @inertiajs/vite
```
:::
### Configure Vite
@available\_since core=3.0.0
Add the Inertia plugin to your `vite.config.js` file.
```js
import inertia from '@inertiajs/vite'
import RubyPlugin from 'vite-plugin-ruby'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
RubyPlugin(),
inertia(),
// ...
],
})
```
### Initialize the Inertia app
Update your main JavaScript file to boot your Inertia app. The Vite plugin handles page resolution and mounting automatically, so a minimal entry point is all you need.
:::tabs key:frameworks
\== Vue
```js
import { createInertiaApp } from '@inertiajs/vue3'
createInertiaApp()
```
\== React
```js
import { createInertiaApp } from '@inertiajs/react'
createInertiaApp()
```
\== Svelte
```js
import { createInertiaApp } from '@inertiajs/svelte'
createInertiaApp()
```
:::
The plugin generates a default resolver that looks for pages in both `./pages` and `./Pages` directories, and the app mounts automatically.
### React Strict Mode
@available\_since core=3.0.0
The React adapter supports enabling React's [Strict Mode](https://react.dev/reference/react/StrictMode) via the `strictMode` option.
```jsx
createInertiaApp({
strictMode: true,
// ...
})
```
### Pages Shorthand
@available\_since core=3.0.0
You may use the `pages` shorthand to customize which directory to search for page components.
:::tabs key:frameworks
\== Vue
```js
import { createInertiaApp } from '@inertiajs/vue3'
createInertiaApp({
pages: './AppPages',
// ...
})
```
\== React
```js
import { createInertiaApp } from '@inertiajs/react'
createInertiaApp({
pages: './AppPages',
// ...
})
```
\== Svelte
```js
import { createInertiaApp } from '@inertiajs/svelte'
createInertiaApp({
pages: './AppPages',
// ...
})
```
:::
An object may also be provided for more control over how pages are resolved. You only need to include the options you wish to customize.
```js
createInertiaApp({
pages: {
path: './Pages',
extension: '.tsx',
lazy: true,
transform: (name, page) => name.replace('/', '-'),
},
})
```
| Option | Description |
| ----------- | -------------------------------------------------------------------------------------------------------------------- |
| `path` | The directory to search for page components. |
| `extension` | A string or array of file extensions (e.g., `'.tsx'` or `['.tsx', '.jsx']`). Defaults to your framework's extension. |
| `lazy` | Whether to lazy-load page components. Defaults to `true`. See [code splitting](/guide/code-splitting). |
| `transform` | A callback that receives the page name and page object, returning a transformed name. |
## Customizing the App
Sometimes you may wish to customize the app instance, for example to register plugins, wrap with providers, or set context values. Pass the `withApp` callback to `createInertiaApp` to hook into the app before it renders.
:::tabs key:frameworks
\== Vue
```js
import { createInertiaApp } from '@inertiajs/vue3'
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
// ...
})
createInertiaApp({
withApp(app) {
app.use(i18n)
},
})
```
\== React
```jsx
import { createInertiaApp } from '@inertiajs/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient()
createInertiaApp({
withApp(app) {
return