Skip to content

Client-Side Setup ​

Once you have your server-side framework configured, 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.

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.

bash
npm install vue @vitejs/plugin-vue

Then, add the framework plugin to your vite.config.js file.

js
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [RubyPlugin(), vue()],
})

For more information on configuring these plugins, consult Vite Rails documentation.

Installation ​

NOTE

You can skip this step if you have already executed the Rails generator.

Install dependencies ​

Available since:@inertiajs/core:3.0.0

Install the Inertia client-side adapter and Vite plugin.

NOTE

The @inertiajs/vite plugin supports Vite 7 and Vite 8.

bash
npm install @inertiajs/vue3 @inertiajs/vite

Configure Vite ​

Available since:@inertiajs/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.

js
import { createInertiaApp } from '@inertiajs/vue3'

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:@inertiajs/core:3.0.0

The React adapter supports enabling React's Strict Mode via the strictMode option.

jsx
createInertiaApp({
  strictMode: true,
  // ...
})

Pages Shorthand ​

Available since:@inertiajs/core:3.0.0

You may use the pages shorthand to customize which directory to search for page components.

js
import { createInertiaApp } from '@inertiajs/vue3'

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('/', '-'),
  },
})
OptionDescription
pathThe directory to search for page components.
extensionA string or array of file extensions (e.g., '.tsx' or ['.tsx', '.jsx']). Defaults to your framework's extension.
lazyWhether to lazy-load page components. Defaults to true. See code splitting.
transformA 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.

js
import { createInertiaApp } from '@inertiajs/vue3'
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  // ...
})

createInertiaApp({
  withApp(app) {
    app.use(i18n)
  },
})
The callback receives the Vue app instance, allowing you to call `app.use()`, `app.provide()`, `app.component()`, and any other app-level method.

A second argument provides the current rendering environment via ssr, allowing you to conditionally apply logic based on where the app is running.

js
createInertiaApp({
  withApp(app, { ssr }) {
    app.use(i18n)

    if (!ssr) {
      app.use(browserOnlyPlugin)
    }
  },
})

The second argument also includes the current page object, giving you access to the component name, URL, version, and shared props before the app renders. Useful for configuring plugins or providers with server-driven state like the user's locale.

js
createInertiaApp({
  withApp(app, { page }) {
    const i18n = createI18n({ locale: page.props.locale })

    app.use(i18n)
  },
})

Manual Setup ​

If you prefer not to use the Vite plugin, you may provide the resolve and setup callbacks manually. The resolve callback tells Inertia how to load a page component and receives the component name and the full page object. The setup callback initializes the client-side framework.

NOTE

A manual setup callback prevents the Vite plugin from auto-generating SSR handling. You should create a separate SSR entry point and update your app to use client-side hydration.

js
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'

createInertiaApp({
  resolve: (name) => {
    const pages = import.meta.glob('../pages/**/*.vue')
    return pages[`../pages/${name}.vue`]()
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
})

By default, page components are lazy-loaded, splitting each page into its own bundle. To eagerly bundle all pages into a single file instead, see the code splitting documentation.

Configuring Defaults ​

Available since:@inertiajs/core:2.2.11

You may pass a defaults object to createInertiaApp() to configure default settings for various features. You don't have to pass a default for every key, just the ones you want to tweak.

js
createInertiaApp({
  defaults: {
    form: {
      recentlySuccessfulDuration: 5000,
    },
    prefetch: {
      cacheFor: '1m',
      hoverDelay: 150,
    },
    visitOptions: (href, options) => {
      return {
        headers: {
          ...options.headers,
          'X-Custom-Header': 'value',
        },
      }
    },
  },
  // ...
})

The visitOptions callback receives the target URL and the current visit options, and should return an object with any options you want to override. For more details on the available configuration options, see the forms, prefetching, and manual visits documentation.

Updating Configuration at Runtime ​

You may also update configuration values at runtime using the exported config instance. This is particularly useful when you need to adjust settings based on user preferences or application state.

js
import { config } from '@inertiajs/vue3'

// Set a single value using dot notation...
config.set('form.recentlySuccessfulDuration', 1000)
config.set('prefetch.cacheFor', '5m')

// Set multiple values at once...
config.set({
  'form.recentlySuccessfulDuration': 1000,
  'prefetch.cacheFor': '5m',
})

// Get a configuration value...
const duration = config.get('form.recentlySuccessfulDuration')

Defining a Root Element ​

By default, Inertia assumes that your application's root template has a root element with an id of app. If your application's root element has a different id, you can provide it using the id property.

js
createInertiaApp({
  id: 'my-app',
  // ...
})

If you change the id of the root element, be sure to update it server-side as well.

Content Security Policy ​

Available since:@inertiajs/core:3.1.0

For applications using a Content Security Policy that restricts inline styles, you may pass a nonce to createInertiaApp(). The nonce is applied to the inline styles Inertia injects for the progress bar and the error modal, allowing them through your CSP.

js
createInertiaApp({
  nonce: 'your-csp-nonce',
  // ...
})

In Rails, the nonce comes from your content security policy configuration. The nonce must be allowed for inline styles, so include style-src in content_security_policy_nonce_directives:

ruby
# config/initializers/content_security_policy.rb
Rails.application.configure do
  config.content_security_policy do |policy|
    policy.script_src :self, :https
    policy.style_src :self, :https
    # ...
  end

  config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
  config.content_security_policy_nonce_directives = %w[script-src style-src]
end

To pass the nonce to the client, render csp_meta_tag in the <head> of your root template. The default Rails layout already includes it.

erb
<head>
  <%= csp_meta_tag %>
  <!-- ... -->
</head>

Then read the nonce from the meta tag when creating your Inertia app. The check for document keeps the entry point working during server-side rendering, where no DOM exists:

js
const nonce =
  typeof document === 'undefined'
    ? undefined
    : document
        .querySelector('meta[name="csp-nonce"]')
        ?.getAttribute('content') || undefined

createInertiaApp({
  nonce,
  // ...
})
Available since:inertia_rails:3.21.2

Inertia Rails also adds the nonce to the <script> element that holds the initial page data, so you don't need to configure it yourself.

HTTP Client ​

Available since:@inertiajs/core:3.0.0

Unlike Inertia 2 and earlier, Inertia 3 uses a built-in XHR client for all requests. No additional HTTP libraries like Axios are required.

Using Axios ​

You may provide the axiosAdapter as the http option when creating your Inertia app. This is useful when your application requires a custom Axios instance.

js
import { axiosAdapter } from '@inertiajs/core'

createInertiaApp({
  http: axiosAdapter(),
  // ...
})

A custom Axios instance may also be provided to the adapter.

js
import axios from 'axios'
import { axiosAdapter } from '@inertiajs/core'

const instance = axios.create({
  // ...
})

createInertiaApp({
  http: axiosAdapter(instance),
  // ...
})

Interceptors ​

The built-in XHR client supports interceptors for modifying requests, inspecting responses, or handling errors. These interceptors apply to all HTTP requests made by Inertia, including those from the router, useForm, <Form>, and useHttp.

js
import { http } from '@inertiajs/vue3'

const removeRequestHandler = http.onRequest((config) => {
  config.headers['X-Custom-Header'] = 'value'
  return config
})

const removeResponseHandler = http.onResponse((response) => {
  console.log('Response status:', response.status)
  return response
})

const removeErrorHandler = http.onError((error) => {
  console.error('Request failed:', error)
})

// Remove a handler when it's no longer needed...
removeRequestHandler()

Each on* method returns a cleanup function that removes the handler when called. Request handlers receive the request config and must return it (modified or not). Response handlers receive the response and must also return it. Handlers may be asynchronous.

Custom HTTP Client ​

For full control over how requests are made, you may provide a completely custom HTTP client via the http option. A custom client must implement the request method, which receives an HttpRequestConfig and returns a promise resolving to an HttpResponse. Review the xhrHttpClient.ts source for a reference implementation.