inertia-rails-controllers

ALWAYS `render inertia: { key: data }` to pass data as props — instance variables are NOT auto-passed (only alba-inertia does that). Rails controller patterns for Inertia.js: render inertia, prop types (defer, optional, merge, scroll), shared data, flash, PRG redirects, validation errors. Use when w

By inertia-rails · 449 installs

npx skills add inertia-rails/skills --skill inertia-rails-controllers

Source repository · Upstream listing

Inertia Rails Controllers Server side patterns for Rails controllers serving Inertia responses. Before adding a prop, ask: Needed on every page? → inertia share in a base controller ( InertiaController ), not a per action prop Expensive to compute? → InertiaRails.defer — page loads fast, data streams in after Only needed on partial reload? → InertiaRails.optional — skipped on initial load Reference data that rarely changes? → InertiaRails.once — cached across navigations NEVER: Use redirect to 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 inertia location (returns 409 + X Inertia Location header). Use errors.full messages 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.to hash(true) . Use inertia.defer , Inertia.defer , or inertia rails.defer — the correct syntax is InertiaRails.defer { ... } . All prop helpers are module methods on the InertiaRails constant. Assume instance variables are auto passed as props — they are NOT (unless alba inertia gem is configured). Every action that passes props to the frontend MUST call render inertia: { key: data } . Use success / error as flash keys without updating config.flash keys — Rails defaults to notice / alert . Custom keys must be added to both the initializer config and the FlashData TypeScript type. Render Syntax default render: true TRAP: This setting only auto infers the component name from controller/action — it does NOT auto pass instance variables as props. Writing @posts = Post.all in an action with default render: true renders the correct component but sends zero data to the frontend. Instance variables are only auto serialized as props when alba inertia gem is configured — check Gemfile before relying on this. Without it, you MUST use render inertia: { posts: data } to pass any data to the page. Empty actions ( def index; end ) 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 call render inertia: with data. Situation Syntax Component path Action loads data render inertia: { users: data } Inferred from controller/action Action loads NO data (static page) Empty action or render inertia: {} Inferred from controller/action Rendering a different page render inertia: 'errors/show', props: { error: e } Explicit path Rule of thumb: If your action touches the database, it MUST call render inertia: with data. If the action body is empty, the page receives only shared props (from inertia share ). 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. Prop Types InertiaRails.defer — NOT inertia.defer , NOT Inertia.defer . All prop helpers are module methods on InertiaRails . Type Syntax Behavior Regular { key: value } Always evaluated, always included Lazy { expensive value } Included on initial page render, lazily evaluated on partial reloads Optional InertiaRails.optional { ... } Only evaluated on partial reload requesting it Defer InertiaRails.defer { ... } Loaded after initial page render Defer (grouped) InertiaRails.defer(group: 'name') { ... } Grouped deferred — fetched in parallel Once InertiaRails.once { ... } Resolved once, remembered across navigations Merge InertiaRails.merge { ... } Appended to existing array (infinite scroll) Deep merge InertiaRails.deep merge { ... } Deep merged into existing object Always InertiaRails.always { ... } Included even in partial reloads Scroll InertiaRails.scroll { ... } Scroll aware prop for infinite scroll Deferred Props — Full Stack Example Server defers slow data, client shows fallback then swaps in content: Shared Data Use inertia share in controllers — it needs controller context ( current user , request). The initializer only handles config. settings (version, flash keys). Lambda and action scoped variants are in [ references/configuration.md ](references/configuration.md). Evaluation order: Multiple inertia share 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. Flash Messages Flash is automatic. Configure exposed keys if needed: Use standard Rails flash in controllers: Redirects & Validation Errors After create/update/delete, always redirect (Post Redirect Get). Standard Rails redirect to works. The Inertia specific part is validation error handling: to hash vs to hash(true) : to hash gives { name: ["can't be blank"] } , to hash(true) gives { name: ["Name can't be blank"] } . Keys must match input name attributes — mismatched keys mean errors won't display next to the right field. NEVER use errors.full messages — it produces flat strings without field keys, so errors can't be mapped to the corresponding input fields on the frontend. Authorization as Props Pass permissions as per resource can hash — frontend controls visibility, server enforces access. See inertia rails controllers + inertia rails pages skills. MANDATORY — READ ENTIRE FILE when implementing authorization props: [ references/authorization.md ](references/authorization.md) (~40 lines) — full stack can pattern with Action Policy/Pundit/CanCanCan examples. Do NOT load if not passing permission data to the frontend. External Redirects ( inertia location ) CRITICAL: redirect to 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. inertia location returns 409 with X Inertia Location header, which tells the client to do window.location = url . Use inertia location for any URL outside the Inertia app: payment providers, OAuth, external services. History Encryption Encrypts page data in browser history state — config.encrypt history = Rails.env.production? . Use redirect to path, inertia: { clear history: true } on logout/role change. Full setup with server side and client side examples is in [ references/configuration.md ](references/configuration.md). Configuration See [ references/configuration.md ](references/configuration.md) for all InertiaRails.configure options (version, encrypt history, flash keys, etc.). Troubleshooting Symptom Cause Fix 302 loop on Stripe/OAuth redirect redirect to for external URL Use inertia location — it returns 409 + X Inertia Location header Errors don't display next to fields Error keys don't match input name to hash keys must match input name attributes exactly TS2305: postsPath not found in @/routes js routes not regenerated after adding routes Run rails js routes:generate after changing config/routes.rb Related Skills Form error display → inertia rails forms Flash toast UI → inertia rails pages (access) + shadcn inertia (Sonner) Deferred on client → inertia rails pages ( <Deferred component) Type safe props → inertia rails typescript or alba inertia (serializers) Testing → inertia rails testing References MANDATORY — READ ENTIRE FILE when using advanced prop types ( merge , scroll , deep merge ) or combining multiple prop options: [ references/prop types.md ](references/prop types.md) (~180 lines) — detailed behavior, edge cases, and combination rules for all prop types. Do NOT load prop types.md for basic defer , optional , once , or always usage — the table above is sufficient. Load [ references/configuration.md ](references/configuration.md) (~180 lines) only when setting up InertiaRails.configure for the first time or debugging configuration issues. Do NOT load for routine controller work.