inertia-rails-controllers
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseInertia Rails Controllers
Inertia Rails 控制器
Server-side patterns for Rails controllers serving Inertia responses.
Before adding a prop, ask:
- Needed on every page? → in a base controller (
inertia_share), not a per-action propInertiaController - Expensive to compute? → — page loads fast, data streams in after
InertiaRails.defer - Only needed on partial reload? → — skipped on initial load
InertiaRails.optional - Reference data that rarely changes? → — cached across navigations
InertiaRails.once
NEVER:
- Use for external URLs (Stripe, OAuth, SSO) — it returns 302 but the Inertia client tries to parse the response as JSON, causing a broken redirect. Use
redirect_to(returns 409 +inertia_locationheader).X-Inertia-Location - Use for validation errors — it produces flat strings without field keys, so errors can't be mapped to the corresponding input fields on the frontend. Use
errors.full_messages.errors.to_hash(true) - Use ,
inertia.defer, orInertia.defer— the correct syntax isinertia_rails.defer. All prop helpers are module methods on theInertiaRails.defer { ... }constant.InertiaRails - Assume instance variables are auto-passed as props — they are NOT (unless gem is configured). Every action that passes props to the frontend MUST call
alba-inertia.render inertia: { key: data } - Use /
successas flash keys without updatingerror— Rails defaults toconfig.flash_keys/notice. Custom keys must be added to both the initializer config and thealertTypeScript type.FlashData
为返回Inertia响应的Rails控制器提供服务端开发模式。
添加prop前请先确认:
- 是否每个页面都需要? → 在基础控制器()中使用
InertiaController,不要作为单个动作的propinertia_share - 计算成本高吗? → 使用——页面快速加载,数据后续流式传输
InertiaRails.defer - 仅在局部重载时需要? → 使用——初始加载时会跳过
InertiaRails.optional - 引用极少变更的参考数据? → 使用——在导航间缓存数据
InertiaRails.once
绝对禁止:
- 对外部URL(Stripe、OAuth、SSO)使用——它返回302状态码,但Inertia客户端会尝试将响应解析为JSON,导致重定向失败。请使用
redirect_to(返回409状态码 +inertia_location请求头)。X-Inertia-Location - 对验证错误使用——它会生成无字段键的扁平字符串,导致前端无法将错误映射到对应的输入字段。请使用
errors.full_messages。errors.to_hash(true) - 使用、
inertia.defer或Inertia.defer——正确语法是inertia_rails.defer。所有prop助手都是InertiaRails.defer { ... }常量的模块方法。InertiaRails - 假设实例变量会自动作为prop传递——事实并非如此(除非配置了gem)。每个向前端传递prop的动作都必须调用
alba-inertia。render inertia: { key: data } - 在未更新的情况下使用
config.flash_keys/success作为flash键——Rails默认使用error/notice。自定义键必须同时添加到初始化器配置和alertTypeScript类型中。FlashData
Render Syntax
渲染语法
TRAP: This setting only auto-infers the component name from controller/action — it does NOT auto-pass instance variables as props. Writingdefault_render: truein an action with@posts = Post.allrenders the correct component but sends zero data to the frontend. Instance variables are only auto-serialized as props whendefault_render: truegem is configured — checkalba-inertiabefore relying on this. Without it, you MUST useGemfileto pass any data to the page.render inertia: { posts: data }Empty actions () are correct ONLY for pages that need no data (e.g., a static dashboard page, a login form). If the action queries the database, it MUST calldef index; endwith data.render inertia:
| Situation | Syntax | Component path |
|---|---|---|
| Action loads data | | Inferred from controller/action |
| Action loads NO data (static page) | Empty action or | Inferred from controller/action |
| Rendering a different page | | Explicit path |
Rule of thumb: If your action touches the database, it MUST call with data.
If the action body is empty, the page receives only shared props (from ).
render inertia:inertia_shareruby
undefined陷阱: 此设置仅会根据控制器/动作自动推断组件名称——不会自动传递实例变量作为prop。在开启default_render: true的动作中编写default_render: true会渲染正确的组件,但不会向前端发送任何数据。 只有在配置了@posts = Post.allgem时,实例变量才会自动序列化为prop——使用前请检查alba-inertia。如果没有配置该gem,必须使用Gemfile才能向页面传递数据。render inertia: { posts: data }空动作()仅适用于不需要数据的页面(例如静态仪表盘页面、登录表单)。如果动作需要查询数据库,则必须调用带数据的def index; end。render inertia:
| 场景 | 语法 | 组件路径 |
|---|---|---|
| 动作加载数据 | | 根据控制器/动作自动推断 |
| 动作不加载数据(静态页面) | 空动作或 | 根据控制器/动作自动推断 |
| 渲染其他页面 | | 显式指定路径 |
经验法则: 如果你的动作需要查询数据库,就必须调用带数据的。
如果动作体为空,页面只会接收来自的共享prop。
render inertia:inertia_shareruby
undefinedCORRECT — data passed as props
正确写法——数据作为prop传递
def index
render inertia: { users: users_data, stats: InertiaRails.defer { ExpensiveQuery.run } }
end
def index
render inertia: { users: users_data, stats: InertiaRails.defer { ExpensiveQuery.run } }
end
CORRECT — static page, no data needed
正确写法——静态页面,无需数据
def index; end
def index; end
WRONG — @posts is NEVER sent to the frontend (without alba-inertia)
错误写法——@posts永远不会发送到前端(未配置alba-inertia时)
def index
@posts = Post.all
end
> **Note:** If the project uses the `alba-inertia` gem (check `Gemfile`), instance
> variables are auto-serialized as props and explicit `render inertia:` is not needed.
> See the `alba-inertia` skill for that convention.def index
@posts = Post.all
end
> 注意:如果项目使用了`alba-inertia` gem(请检查`Gemfile`),实例变量会自动序列化为prop,无需显式调用`render inertia:`。
> 相关约定请查看`alba-inertia`技能文档。Prop Types
Prop 类型
InertiaRails.deferinertia.deferInertia.deferInertiaRails| Type | Syntax | Behavior |
|---|---|---|
| Regular | | Always evaluated, always included |
| Lazy | | Included on initial page render, lazily evaluated on partial reloads |
| Optional | | Only evaluated on partial reload requesting it |
| Defer | | Loaded after initial page render |
| Defer (grouped) | | Grouped deferred — fetched in parallel |
| Once | | Resolved once, remembered across navigations |
| Merge | | Appended to existing array (infinite scroll) |
| Deep merge | | Deep merged into existing object |
| Always | | Included even in partial reloads |
| Scroll | | Scroll-aware prop for infinite scroll |
ruby
def index
render inertia: {
filters: filter_params,
messages: -> { messages_scope.as_json },
stats: InertiaRails.defer { Dashboard.stats },
chart: InertiaRails.defer(group: 'analytics') { Dashboard.chart },
countries: InertiaRails.once { Country.pluck(:name, :code) },
posts: InertiaRails.merge { @posts.as_json },
csrf: InertiaRails.always { form_authenticity_token },
}
endInertiaRails.deferinertia.deferInertia.deferInertiaRails| 类型 | 语法 | 行为 |
|---|---|---|
| 常规 | | 始终执行,始终包含 |
| 延迟加载 | | 初始页面渲染时包含,局部重载时延迟执行 |
| 可选 | | 仅在请求该prop的局部重载时执行 |
| 延迟传递 | | 初始页面渲染完成后加载 |
| 分组延迟传递 | | 分组延迟——并行获取数据 |
| 单次加载 | | 仅解析一次,在导航间缓存 |
| 合并 | | 追加到现有数组(无限滚动场景) |
| 深度合并 | | 深度合并到现有对象 |
| 始终包含 | | 即使在局部重载中也会包含 |
| 滚动感知 | | 适用于无限滚动的滚动感知prop |
ruby
def index
render inertia: {
filters: filter_params,
messages: -> { messages_scope.as_json },
stats: InertiaRails.defer { Dashboard.stats },
chart: InertiaRails.defer(group: 'analytics') { Dashboard.chart },
countries: InertiaRails.once { Country.pluck(:name, :code) },
posts: InertiaRails.merge { @posts.as_json },
csrf: InertiaRails.always { form_authenticity_token },
}
endDeferred Props — Full Stack Example
延迟Prop——全栈示例
Server defers slow data, client shows fallback then swaps in content:
ruby
undefined服务端延迟加载慢数据,客户端先显示占位内容,之后替换为真实内容:
ruby
undefinedController
控制器
def show
render inertia: {
basic_stats: Stats.quick_summary,
analytics: InertiaRails.defer { Analytics.compute_slow },
}
end
```tsx
// Page component — child reads deferred prop from page props
import { Deferred, usePage } from '@inertiajs/react'
export default function Dashboard({ basic_stats }: Props) {
return (
<>
<QuickStats data={basic_stats} />
<Deferred data="analytics" fallback={<div>Loading analytics...</div>}>
<AnalyticsPanel />
</Deferred>
</>
)
}
function AnalyticsPanel() {
const { analytics } = usePage<{ analytics: Analytics }>().props
return <div>{analytics.revenue}</div>
}def show
render inertia: {
basic_stats: Stats.quick_summary,
analytics: InertiaRails.defer { Analytics.compute_slow },
}
end
```tsx
// 页面组件——子组件从页面props中读取延迟prop
import { Deferred, usePage } from '@inertiajs/react'
export default function Dashboard({ basic_stats }: Props) {
return (
<>
<QuickStats data={basic_stats} />
<Deferred data="analytics" fallback={<div>正在加载分析数据...</div>}>
<AnalyticsPanel />
</Deferred>
</>
)
}
function AnalyticsPanel() {
const { analytics } = usePage<{ analytics: Analytics }>().props
return <div>{analytics.revenue}</div>
}Shared Data
共享数据
Use in controllers — it needs controller context (,
request). The initializer only handles settings (version, flash_keys).
inertia_sharecurrent_userconfig.*ruby
class ApplicationController < ActionController::Base
# Static
inertia_share app_name: 'MyApp'
# Using lambdas (most common)
inertia_share auth: -> { { user: current_user&.as_json(only: [:id, :name, :email, :role]) } }
# Conditional
inertia_share if: :user_signed_in? do
{ notifications: -> { current_user.unread_notifications_count } }
end
endLambda and action-scoped variants are in .
references/configuration.mdEvaluation order: Multiple calls merge top-down. If a child
controller shares the same key as a parent, the child's value wins. Block and lambda
shares are lazily evaluated per-request — they don't run for non-Inertia requests.
inertia_share在控制器中使用——它需要控制器上下文(如、request)。初始化器仅处理设置(如版本、flash_keys)。
inertia_sharecurrent_userconfig.*ruby
class ApplicationController < ActionController::Base
# 静态数据
inertia_share app_name: 'MyApp'
# 使用lambda(最常用方式)
inertia_share auth: -> { { user: current_user&.as_json(only: [:id, :name, :email, :role]) } }
# 条件式共享
inertia_share if: :user_signed_in? do
{ notifications: -> { current_user.unread_notifications_count } }
end
endLambda和动作作用域的变体请查看。
references/configuration.md执行顺序: 多次调用会自上而下合并。如果子控制器与父控制器共享同一个键,子控制器的值会覆盖父控制器的。块和lambda形式的共享数据会按请求延迟执行——非Inertia请求不会触发执行。
inertia_shareFlash Messages
Flash 消息
Flash is automatic. Configure exposed keys if needed:
ruby
undefinedFlash消息会自动处理。如需暴露自定义键,请进行配置:
ruby
undefinedconfig/initializers/inertia_rails.rb
config/initializers/inertia_rails.rb
InertiaRails.configure do |config|
config.flash_keys = %i[notice alert toast] # default: %i[notice alert]
end
Use standard Rails flash in controllers:
```ruby
redirect_to users_path, notice: "User created!"InertiaRails.configure do |config|
config.flash_keys = %i[notice alert toast] # 默认值:%i[notice alert]
end
在控制器中使用标准Rails flash:
```ruby
redirect_to users_path, notice: "用户创建成功!"or
或者
flash.alert = "Something went wrong"
redirect_to users_path
undefinedflash.alert = "出现错误"
redirect_to users_path
undefinedRedirects & Validation Errors
重定向与验证错误
After create/update/delete, always redirect (Post-Redirect-Get). Standard Rails
works. The Inertia-specific part is validation error handling:
redirect_toruby
def create
@user = User.new(user_params)
if @user.save
redirect_to users_path, notice: "Created!"
else
redirect_back_or_to new_user_path, inertia: { errors: @user.errors.to_hash(true) }
end
endto_hashto_hash(true)to_hash{ name: ["can't be blank"] }to_hash(true){ name: ["Name can't be blank"] }nameNEVER use — it produces flat strings without field keys,
so errors can't be mapped to the corresponding input fields on the frontend.
errors.full_messages创建/更新/删除操作完成后,始终使用重定向(Post-Redirect-Get模式)。标准Rails的可以正常工作。Inertia特有的部分是验证错误处理:
redirect_toruby
def create
@user = User.new(user_params)
if @user.save
redirect_to users_path, notice: "创建成功!"
else
redirect_back_or_to new_user_path, inertia: { errors: @user.errors.to_hash(true) }
end
endto_hashto_hash(true)to_hash{ name: ["can't be blank"] }to_hash(true){ name: ["Name can't be blank"] }name绝对不要使用——它会生成无字段键的扁平字符串,导致前端无法将错误映射到对应的输入字段。
errors.full_messagesAuthorization as Props
作为Prop的授权信息
Pass permissions as per-resource hash — frontend controls visibility,
server enforces access. See + skills.
caninertia-rails-controllersinertia-rails-pagesMANDATORY — READ ENTIRE FILE when implementing authorization props:
(~40 lines) — full-stack
pattern with Action Policy/Pundit/CanCanCan examples.
references/authorization.mdcanDo NOT load if not passing permission data to the frontend.
按资源传递哈希形式的权限——前端控制可见性,服务端强制执行访问控制。请查看 + 技能文档。
caninertia-rails-controllersinertia-rails-pages强制要求——实现授权prop时请通读整个文件:
(约40行)——包含Action Policy/Pundit/CanCanCan示例的全栈模式。
references/authorization.mdcan如果不需要向前端传递权限数据,请不要加载此内容。
External Redirects (inertia_location
)
inertia_location外部重定向(inertia_location
)
inertia_locationCRITICAL: for external URLs breaks Inertia — the client
receives a 302 but tries to handle it as an Inertia response (JSON), not a
full page redirect. returns 409 with
header, which tells the client to do .
redirect_toinertia_locationX-Inertia-Locationwindow.location = urlruby
undefined重点注意: 对外部URL使用会破坏Inertia——客户端收到302状态码后会尝试将其作为Inertia响应(JSON)处理,而非整页重定向。返回409状态码和请求头,告知客户端执行。
redirect_toinertia_locationX-Inertia-Locationwindow.location = urlruby
undefinedStripe checkout — MUST use inertia_location, not redirect_to
Stripe结账——必须使用inertia_location,不能使用redirect_to
def create
checkout_session = Current.user.payment_processor.checkout(
mode: "payment",
line_items: "price_xxx",
success_url: enrollments_url,
cancel_url: course_url(@course),
)
inertia_location checkout_session.url
end
Use `inertia_location` for any URL outside the Inertia app: payment
providers, OAuth, external services.def create
checkout_session = Current.user.payment_processor.checkout(
mode: "payment",
line_items: "price_xxx",
success_url: enrollments_url,
cancel_url: course_url(@course),
)
inertia_location checkout_session.url
end
对所有Inertia应用外的URL使用`inertia_location`:支付服务商、OAuth、外部服务等。History Encryption
历史记录加密
Encrypts page data in browser history state — .
Use on logout/role change.
Full setup with server-side and client-side examples is in
.
config.encrypt_history = Rails.env.production?redirect_to path, inertia: { clear_history: true }references/configuration.md对浏览器历史记录中的页面数据进行加密——。
在登出/角色变更时使用。
包含服务端和客户端示例的完整设置请查看
。
config.encrypt_history = Rails.env.production?redirect_to path, inertia: { clear_history: true }references/configuration.mdConfiguration
配置
See for all
options (version, encrypt_history, flash_keys, etc.).
references/configuration.mdInertiaRails.configure所有选项(版本、encrypt_history、flash_keys等)请查看。
InertiaRails.configurereferences/configuration.mdTroubleshooting
故障排查
| Symptom | Cause | Fix |
|---|---|---|
| 302 loop on Stripe/OAuth redirect | | Use |
| Errors don't display next to fields | Error keys don't match input | |
TS2305: | js-routes not regenerated after adding routes | Run |
| 症状 | 原因 | 修复方案 |
|---|---|---|
| Stripe/OAuth重定向时出现302循环 | 对外部URL使用了 | 使用 |
| 错误无法显示在对应字段旁 | 错误键与输入框 | |
TS2305: | 添加路由后未重新生成js-routes | 修改 |
Related Skills
相关技能
- Form error display →
inertia-rails-forms - Flash toast UI → (access) +
inertia-rails-pages(Sonner)shadcn-inertia - Deferred on client → (
inertia-rails-pagescomponent)<Deferred> - Type-safe props → or
inertia-rails-typescript(serializers)alba-inertia - Testing →
inertia-rails-testing
- 表单错误展示 →
inertia-rails-forms - Flash消息提示UI → (权限) +
inertia-rails-pages(Sonner组件)shadcn-inertia - 客户端延迟处理 → (
inertia-rails-pages组件)<Deferred> - 类型安全Prop → 或
inertia-rails-typescript(序列化器)alba-inertia - 测试 →
inertia-rails-testing
References
参考资料
MANDATORY — READ ENTIRE FILE when using advanced prop types (,
, ) or combining multiple prop options:
(~180 lines) — detailed behavior,
edge cases, and combination rules for all prop types.
mergescrolldeep_mergereferences/prop-types.mdDo NOT load for basic , , , or
usage — the table above is sufficient.
prop-types.mddeferoptionaloncealwaysLoad (~180 lines) only when
setting up for the first time or debugging configuration
issues. Do NOT load for routine controller work.
references/configuration.mdInertiaRails.configure强制要求——使用高级prop类型(、、)或组合多个prop选项时请通读整个文件:
(约180行)——包含所有prop类型的详细行为、边界情况和组合规则。
mergescrolldeep_mergereferences/prop-types.md如果仅使用基础的、、或,无需加载——上述表格内容已足够。
deferoptionaloncealwaysprop-types.md仅在首次设置或排查配置问题时加载(约180行)。日常控制器开发无需加载此文件。
InertiaRails.configurereferences/configuration.md