---
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.
With Vite Ruby, cache busting is done automatically: built files get a content hash in their names. The [Rails generator](/guide/server-side-setup#rails-generator) also sets `config.version = ViteRuby.digest` in `config/initializers/inertia_rails.rb`, so the Inertia version changes whenever your frontend files change.
## Background Requests
@available\_since rails=3.23.0 core=3.6.0
A version change detected on a background request, such as polling or a `router.reload()`, does not force a full page visit. Reloading a request the user never initiated could wipe out unsaved state, so the current page stays untouched and the next user-initiated visit picks up the new assets. You may hook into the [`location` event](/guide/events#location) to detect an asset version change and show a notification instead of reloading.
The client tells a version change apart from a redirect by the `X-Inertia-Version` header, which Inertia Rails sends with the version change response.
## 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.
Rails 8 also ships a session-based authentication generator, which adds users, sessions, and password resets to your app:
```bash
bin/rails generate authentication
```
The generator creates ERB views for the sign-in and password reset forms. To use Inertia pages instead, render them from the generated `SessionsController` and `PasswordsController`.
> \[!TIP]
> The [Inertia Rails starter kits](/guide/starter-kits) provide out-of-the-box scaffolding for new Inertia applications, including authentication.
---
---
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
* [Katara](https://trykatara.com) - A reverse job board for software engineers
* [OG Pilot](https://ogpilot.com) — A dynamic Open Graph image generator tool
* [Switch Kanban](https://switchkanban.com.br) — Project management tool for software houses
* [railsfullstack.com](https://railsfullstack.com) - Ruby on Rails back-end & full-stack job board
## 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/big-integers.md
---
# Big Integers
@available\_since rails=3.23.0 core=3.8.0
JavaScript can't represent integers above `2**53 - 1` exactly, so it rounds them while parsing JSON. An ID like `900719925474099988` from a `bigint` column or a generator like Twitter's Snowflake reaches your components as `900719925474100000`. With big integer support enabled, Inertia Rails sends those values in a form the client turns into native `BigInt` values, following the JSON convention [documented on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#use_within_json).
## Enabling Big Integers
Big integer support is disabled by default. It's enabled entirely on the server, so there is nothing to configure on the client. Set the `preserve_big_integers` config value to `true`, or set the `INERTIA_PRESERVE_BIG_INTEGERS` environment variable.
```ruby
# config/initializers/inertia_rails.rb
InertiaRails.configure do |config|
config.preserve_big_integers = true
end
```
You can also enable it for all actions in a controller:
```ruby
class OrdersController < ApplicationController
inertia_config(preserve_big_integers: true)
end
```
Or for a single response, which is handy when only a few pages deal with large integers. Passing `false` opts a single response out once the feature is enabled in your configuration.
```ruby
render inertia: { order: order }, preserve_big_integers: true
render inertia: { report: report }, preserve_big_integers: false
```
## How Values Are Transported
Integers outside JavaScript's safe range are wrapped in a marker object, and the page is flagged with `preserveBigIntegers: true`. The client turns each marker back into a `BigInt`, so your component receives the exact value.
```ruby
render inertia: { id: 900719925474099988 }
# The response carries "id": {"$bigint": "900719925474099988"}
```
:::tabs key:frameworks
\== Vue
```vue
User {{ id }}
User {String(id)}
// User 900719925474099988 } ``` \== Svelte ```svelteUser {id}
``` ::: Integers within the safe range stay regular numbers, so the same prop may arrive as a number or a `BigInt` depending on its value. [Flash data](/guide/flash-data) gets the same treatment as props. Inertia Rails looks for big integers in the finished page, after [`prop_transformer`](/guide/configuration#prop-transformer) runs. It finds them inside Active Record models, objects with `as_json` or `to_inertia`, [cached props](/guide/cached-props), JSON fragments, and [infinite scroll](/guide/infinite-scroll) page numbers. Partial reloads, deferred props, and the page sent to the [SSR server](/guide/server-side-rendering) are covered too. In your root template, [`inertia_page`](/guide/responses#inertia-page) holds the markers instead of the integers. The client only revives markers on pages with the flag, so an app that already sends its own `{"$bigint": "..."}` objects keeps receiving them as plain objects everywhere else. > \[!WARNING] > On pages with the flag, the client turns every `{"$bigint": "..."}` object into a `BigInt`, including ones that come from user data. A marker that holds anything but digits makes the page fail to load. ## Submitting Big Integers A `BigInt` submitted through the router, a form, or [Precognition](/guide/precognition) is sent as its digits, the same way form data and query strings send numbers. Your controller receives a numeric string, which Active Record casts as usual. ```js router.post('/orders', { id: props.order.id }) ``` ```ruby def create order = Order.find(params.require(:id)) # ... end ``` The [`useHttp`](/guide/http-requests) hook doesn't handle `BigInt` values, since it's often used to call third-party APIs. Convert them to strings before sending. ## Testing The Inertia Rails [testing helpers](/guide/testing) turn markers back into integers, so you can assert against the values you passed to the response. :::tabs key:tests \== RSpec ```ruby expect(inertia).to have_props(order: { id: 900719925474099988 }) expect(inertia.props[:order][:id]).to eq 900719925474099988 ``` \== Minitest ```ruby assert_inertia_props order: { id: 900719925474099988 } assert_equal 900719925474099988, inertia.props[:order][:id] ``` ::: --- --- 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/@2/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/@2/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/@2/` 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`, and it works with Inertia responses out of the box. @available\_since rails=3.22.0 The same URL answers with different bodies: an HTML document on the initial visit, an Inertia JSON page on client-side visits, and prop subsets on partial reloads. Inertia Rails folds the Inertia request headers into every ETag automatically, so each representation gets its own ETag and a conditional request never receives a `304 Not Modified` backed by a different representation's body. Plain (non-Inertia) requests are unaffected. > \[!NOTE] > On older adapter versions, differentiate the ETags yourself by adding `etag { request.inertia? }` to your `ApplicationController`. ### Using `stale?` 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 ``` > \[!WARNING] > Automatic differentiation applies to ETags. A `fresh_when last_modified:` call with no `etag` produces no validator to differentiate, so a shared cache that ignores `Vary` can pair a `304` with the wrong representation. Pair `last_modified` with an `etag`. If your conditionally cached responses still carry `Set-Cookie` headers — most shared caches refuse to store those — see [HTTP caching and XSRF cookie refresh](/cookbook/http-caching-and-xsrf-cookie-refresh). ## 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({ pages: '../pages', }) ``` \== React ```js import { createInertiaApp } from '@inertiajs/react' createInertiaApp({ pages: '../pages', }) ``` \== Svelte ```js import { createInertiaApp } from '@inertiajs/svelte' createInertiaApp({ pages: '../pages', }) ``` ::: The `pages` path is relative to the entry point. The Rails generator places the entry point in `app/frontend/entrypoints` and your pages in `app/frontend/pages`, so it uses `../pages`. Without the `pages` option, the plugin looks for pages in the `./pages` and `./Pages` directories next to the entry point. 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) { returnFailed to load permissions.
Failed to load permissions.
Failed to load permissions.
{{ errors.name }}
{{ errors.email }}
Validating...
{errors.name}
} validate('email')} /> {invalid('email') &&{errors.email}
} {validating &&Validating...
} > )}{errors.name}
{/if} validate('email')} /> {#if invalid('email')}{errors.email}
{/if} {#if validating}Validating...
{/if} {/snippet}Valid email address
{{ errors.email }}
Valid email address
} {invalid('email') &&{errors.email}
} > )}Valid email address
{/if} {#if invalid('email')}{errors.email}
{/if} {/snippet}Name has been touched
Name has been touched
} > )}Name has been touched
{/if} {/snippet}{{ form.errors.name }}
{{ form.errors.email }}
Validating...
{errors.name}
} setData('email', e.target.value)} onBlur={() => validate('email')} /> {invalid('email') &&{errors.email}
} {validating &&Validating...
}{form.errors.name}
{/if} form.validate('email')} /> {#if form.invalid('email')}{form.errors.email}
{/if} {#if form.validating}Validating...
{/if}Name has been touched
``` \== React ```jsx setData('name', e.target.value)} onBlur={() => touch('name')} /> setData('email', e.target.value)} onBlur={() => touch('email')} /> {touched('name') &&Name has been touched
} ``` \== Svelte ```svelte form.touch('name')} /> form.touch('email')} /> {#if form.touched('name')}Name has been touched
{/if} ``` ::: #### Options Validation requests are automatically debounced. The first request fires immediately, then subsequent changes are debounced (1500ms by default). You may customize this timeout using `setValidationTimeout()`. :::tabs key:frameworks \== Vue ```js const form = useForm('post', '/users', { name: '', }).setValidationTimeout(500) ``` \== React ```js const form = useForm('post', '/users', { name: '', }) form.setValidationTimeout(500) ``` \== Svelte ```js const form = useForm('post', '/users', { name: '', }) form.setValidationTimeout(500) ``` ::: By default, files are excluded from validation requests to avoid unnecessary uploads. You may enable file validation using `validateFiles()`. :::tabs key:frameworks \== Vue ```js const form = useForm('post', '/users', { avatar: null, }).validateFiles() ``` \== React ```js const form = useForm('post', '/users', { avatar: null, }) form.validateFiles() ``` \== Svelte ```js const form = useForm('post', '/users', { avatar: null, }) form.validateFiles() ``` ::: By default, validation errors are simplified to strings (the first error message). You can indicate you would like all errors as arrays using `withAllErrors()`. :::tabs key:frameworks \== Vue ```js const form = useForm('post', '/users', { name: '', }).withAllErrors() ``` \== React ```js const form = useForm('post', '/users', { name: '', }) form.withAllErrors() ``` \== Svelte ```js const form = useForm('post', '/users', { name: '', }) form.withAllErrors() ``` ::: With Precognition enabled, you may call `submit()` without arguments to dispatch the request to the configured endpoint. ## Server-Side Responses When using Inertia, you don't typically inspect form responses client-side like you would with traditional XHR/fetch requests. Instead, your server-side route or controller issues a [redirect](/guide/redirects) response after processing the form, often redirecting to a success page. ```ruby class UsersController < ApplicationController def create user = User.new(user_params) if user.save redirect_to users_url else redirect_to new_user_url, inertia: { errors: user.errors } end end private def user_params params.expect(user: [:name, :email]) end end ``` This redirect-based approach works with all form submission methods: the `| Name |
|---|
| {{ user.name }} |
| Name |
|---|
| {user.name} |
| Name |
|---|
| {user.name} |
| Name |
|---|
| {{ user.name }} |
| Footer |
| Name |
|---|
| {user.name} |
| Footer |
| Name |
|---|
| {user.name} |
| Footer |
| Name |
|---|
| {{ user.name }} |
| Footer |
| Name |
|---|
| {user.name} |
| Footer |
| Name |
|---|
| {user.name} |
| Footer |
Hello {{ user.name }}, welcome to your first Inertia app!
Hello {user.name}, welcome to your first Inertia app!
Hello {user.name}, welcome to your first Inertia app!
Hello {{ user.name }}, welcome to your first Inertia app!
``` \== React ```jsx import Layout from './Layout' const Welcome = ({ user }) => { return ( <>Hello {user.name}, welcome to your first Inertia app!
> ) } Welcome.layout = (page) =>Hello {user.name}, welcome to your first Inertia app!
``` ::: Vue 3.3+ users may alternatively use [defineOptions](https://vuejs.org/api/sfc-script-setup.html#defineoptions) to define a layout within ` ``` Arrow function components should be wrapped in an array. Without the array, Inertia cannot distinguish them from render functions at runtime: ```jsx const ArrowLayout = ({ children }) =>Hello {{ user.name }}, welcome to your first Inertia app!
``` \== React ```jsx import SiteLayout from './SiteLayout' import NestedLayout from './NestedLayout' const Welcome = ({ user }) => { return ( <>Hello {user.name}, welcome to your first Inertia app!
> ) } Welcome.layout = [SiteLayout, NestedLayout] export default Welcome ``` \== Svelte ```svelteHello {user.name}, welcome to your first Inertia app!
``` ::: ## Default Layouts @available\_since core=3.0.0 The `layout` option in `createInertiaApp` lets you define a default layout for all pages, saving you from defining it on every page individually. Per-page layouts always take precedence over the default. ```js import Layout from './Layout' createInertiaApp({ layout: () => Layout, // ... }) ``` You may also conditionally return a layout based on the page name. For example, you may wish to exclude public pages from the default layout. ```js import Layout from './Layout' createInertiaApp({ layout: (name) => { if (name.startsWith('public/')) { return null } return Layout }, // ... }) ``` The full page object is also available as the second argument, giving you access to the page's URL, props, and other metadata. The `layout` callback supports all layout formats, including arrays for [nested layouts](#nested-layouts), named objects for [named layouts](#targeting-named-layouts), and tuples for [static props](#static-props). ### Using the Resolve Callback You may also set a default layout inside the `resolve` callback by mutating the resolved page component. The callback receives the component name and the full page object, which is useful when you need to conditionally apply layouts based on page data. :::tabs key:frameworks \== Vue ```js import Layout from './Layout' createInertiaApp({ resolve: (name) => { const pages = import.meta.glob('../pages/**/*.vue', { eager: true }) let page = pages[`../pages/${name}.vue`] page.default.layout = page.default.layout || Layout return page }, // ... }) ``` \== React ```jsx import Layout from './Layout' createInertiaApp({ resolve: (name) => { const pages = import.meta.glob('../pages/**/*.jsx', { eager: true }) let page = pages[`../pages/${name}.jsx`] page.default.layout = page.default.layout || ((page) =>