inertia-coder

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Inertia.js + Rails

Inertia.js + Rails

Build modern single-page applications using Inertia.js with React/Vue/Svelte and Rails backend.
使用Inertia.js搭配React/Vue/Svelte前端与Rails后端,构建现代化单页应用。

When to Use This Skill

何时使用此技能

  • Setting up Inertia.js with Rails
  • Creating Inertia page components
  • Handling forms with useForm hook
  • Managing shared props and flash messages
  • Client-side routing without API complexity
  • File uploads with progress tracking
  • 搭建Inertia.js与Rails的集成环境
  • 创建Inertia页面组件
  • 使用useForm钩子处理表单
  • 管理共享props与flash消息
  • 无需复杂API即可实现客户端路由
  • 带进度追踪的文件上传

What is Inertia.js?

什么是Inertia.js?

Inertia.js allows you to build SPAs using classic server-side routing and controllers.
ApproachProsCons
Traditional Rails ViewsSimple, server-renderedLimited interactivity
Rails API + React SPAFull SPA experienceDuplicated routing, complex state
Inertia.jsSPA + server routingBest of both worlds
Inertia.js允许你使用传统的服务端路由与控制器来构建SPA。
方案优点缺点
传统Rails视图简单,服务端渲染交互性有限
Rails API + React SPA完整的SPA体验路由重复,状态管理复杂
Inertia.jsSPA + 服务端路由兼顾两者优势

Quick Setup

快速搭建

ruby
undefined
ruby
undefined

Gemfile

Gemfile

gem 'inertia_rails' gem 'vite_rails'

```bash
bundle install
rails inertia:install  # Choose: React, Vue, or Svelte
ruby
undefined
gem 'inertia_rails' gem 'vite_rails'

```bash
bundle install
rails inertia:install  # Choose: React, Vue, or Svelte
ruby
undefined

config/initializers/inertia_rails.rb

config/initializers/inertia_rails.rb

InertiaRails.configure do |config| config.version = ViteRuby.digest
config.share do |controller| { auth: { user: controller.current_user&.as_json(only: [:id, :name, :email]) }, flash: controller.flash.to_hash } end end
undefined
InertiaRails.configure do |config| config.version = ViteRuby.digest
config.share do |controller| { auth: { user: controller.current_user&.as_json(only: [:id, :name, :email]) }, flash: controller.flash.to_hash } end end
undefined

Controller Pattern

控制器模式

ruby
class ArticlesController < ApplicationController
  def index
    articles = Article.published.order(created_at: :desc)

    render inertia: 'Articles/Index', props: {
      articles: articles.as_json(only: [:id, :title, :excerpt])
    }
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to article_path(@article), notice: 'Article created'
    else
      redirect_to new_article_path, inertia: { errors: @article.errors }
    end
  end
end
ruby
class ArticlesController < ApplicationController
  def index
    articles = Article.published.order(created_at: :desc)

    render inertia: 'Articles/Index', props: {
      articles: articles.as_json(only: [:id, :title, :excerpt])
    }
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to article_path(@article), notice: 'Article created'
    else
      redirect_to new_article_path, inertia: { errors: @article.errors }
    end
  end
end

React Page Component

React页面组件

jsx
// app/frontend/pages/Articles/Index.jsx
import { Link } from '@inertiajs/react'

export default function Index({ articles }) {
  return (
    <div>
      <h1>Articles</h1>
      {articles.map(article => (
        <Link key={article.id} href={`/articles/${article.id}`}>
          <h2>{article.title}</h2>
        </Link>
      ))}
    </div>
  )
}
jsx
// app/frontend/pages/Articles/Index.jsx
import { Link } from '@inertiajs/react'

export default function Index({ articles }) {
  return (
    <div>
      <h1>Articles</h1>
      {articles.map(article => (
        <Link key={article.id} href={`/articles/${article.id}`}>
          <h2>{article.title}</h2>
        </Link>
      ))}
    </div>
  )
}

Forms with useForm

使用useForm处理表单

jsx
import { useForm } from '@inertiajs/react'

export default function New() {
  const { data, setData, post, processing, errors } = useForm({
    title: '',
    body: ''
  })

  function handleSubmit(e) {
    e.preventDefault()
    post('/articles')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={data.title}
        onChange={e => setData('title', e.target.value)}
      />
      {errors.title && <div className="error">{errors.title}</div>}

      <textarea
        value={data.body}
        onChange={e => setData('body', e.target.value)}
      />
      {errors.body && <div className="error">{errors.body}</div>}

      <button disabled={processing}>
        {processing ? 'Creating...' : 'Create'}
      </button>
    </form>
  )
}
jsx
import { useForm } from '@inertiajs/react'

export default function New() {
  const { data, setData, post, processing, errors } = useForm({
    title: '',
    body: ''
  })

  function handleSubmit(e) {
    e.preventDefault()
    post('/articles')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={data.title}
        onChange={e => setData('title', e.target.value)}
      />
      {errors.title && <div className="error">{errors.title}</div>}

      <textarea
        value={data.body}
        onChange={e => setData('body', e.target.value)}
      />
      {errors.body && <div className="error">{errors.body}</div>}

      <button disabled={processing}>
        {processing ? 'Creating...' : 'Create'}
      </button>
    </form>
  )
}

Shared Layout

共享布局

jsx
// app/frontend/layouts/AppLayout.jsx
import { Link, usePage } from '@inertiajs/react'

export default function AppLayout({ children }) {
  const { auth, flash } = usePage().props

  return (
    <div>
      <nav>
        <Link href="/">Home</Link>
        {auth.user ? (
          <Link href="/logout" method="delete">Logout</Link>
        ) : (
          <Link href="/login">Login</Link>
        )}
      </nav>

      {flash.success && <div className="alert-success">{flash.success}</div>}

      <main>{children}</main>
    </div>
  )
}

// Assign layout to page
Index.layout = page => <AppLayout>{page}</AppLayout>
jsx
// app/frontend/layouts/AppLayout.jsx
import { Link, usePage } from '@inertiajs/react'

export default function AppLayout({ children }) {
  const { auth, flash } = usePage().props

  return (
    <div>
      <nav>
        <Link href="/">Home</Link>
        {auth.user ? (
          <Link href="/logout" method="delete">Logout</Link>
        ) : (
          <Link href="/login">Login</Link>
        )}
      </nav>

      {flash.success && <div className="alert-success">{flash.success}</div>}

      <main>{children}</main>
    </div>
  )
}

// Assign layout to page
Index.layout = page => <AppLayout>{page}</AppLayout>

File Upload

文件上传

jsx
import { useForm } from '@inertiajs/react'

const { data, setData, post, progress } = useForm({
  avatar: null
})

<input
  type="file"
  onChange={e => setData('avatar', e.target.files[0])}
/>

{progress && <progress value={progress.percentage} max="100" />}

<button onClick={() => post('/profile/avatar', { forceFormData: true })}>
  Upload
</button>
jsx
import { useForm } from '@inertiajs/react'

const { data, setData, post, progress } = useForm({
  avatar: null
})

<input
  type="file"
  onChange={e => setData('avatar', e.target.files[0])}
/>

{progress && <progress value={progress.percentage} max="100" />}

<button onClick={() => post('/profile/avatar', { forceFormData: true })}>
  Upload
</button>

Best Practices

最佳实践

DO

建议

  • Use
    <Link>
    instead of
    <a>
    tags
  • Share common data via config (auth, flash)
  • Validate on server - client is UX only
  • Show loading states with
    processing
  • Use layouts for consistent navigation
  • 使用
    <Link>
    标签而非
    <a>
    标签
  • 通过配置共享通用数据(如认证信息、flash消息)
  • 在服务端进行验证——客户端验证仅用于提升用户体验
  • 使用
    processing
    状态展示加载中效果
  • 使用布局保持导航一致性

DON'T

避免

  • Don't use
    window.location
    - breaks SPA
  • Don't create REST APIs - Inertia doesn't need them
  • Don't fetch data client-side - server provides props
  • Don't bypass Inertia router - breaks behavior
  • 不要使用
    window.location
    ——会破坏SPA特性
  • 不要创建REST API——Inertia不需要它们
  • 不要在客户端获取数据——由服务端提供props
  • 不要绕过Inertia路由——会破坏原有行为

Detailed References

详细参考资料

For framework-specific patterns:
  • references/react-patterns.md
    - React hooks, TypeScript, advanced patterns
  • references/vue-patterns.md
    - Vue 3 Composition API patterns
  • references/svelte-patterns.md
    - Svelte stores and reactivity patterns
针对各框架的特定模式:
  • references/react-patterns.md
    - React钩子、TypeScript、进阶模式
  • references/vue-patterns.md
    - Vue 3组合式API模式
  • references/svelte-patterns.md
    - Svelte存储与响应式模式