inertia-rails-architecture — Claude Code skill: Inertia Rails patterns
Sets the server-driven patterns for Rails + Inertia.js + React and routes you to the right companion skill when you build a page, form or CRUD.
npx skills add inertia-rails/skills --skill inertia-rails-architecture -a claude-codeWhat inertia-rails-architecture does
The skill lays out a mental model where the server owns routing, data and auth, and React only renders what arrives as props. A decision matrix covers where page data comes from, where global state lives, how forms get submitted and how to refresh part of the data. A second table ranks the rules by impact and explains the reasoning behind each one.
There is also a skill map. A new page with props pulls in inertia-rails-controllers, inertia-rails-pages and inertia-rails-typescript; a form with validation pulls in inertia-rails-forms plus the controllers skill. A separate section covers the cases where Inertia is the wrong tool and you want a plain API endpoint: non-browser consumers, search over large datasets, binary responses such as PDF or CSV exports.
The skill asks you to read references/AGENTS.md in full before building a new page, and references/decision-trees.md when the right pattern is unclear. Skip both for quick questions about a single pattern. Data reaches React through render inertia: { key: value } — @ivars are not passed as props automatically, and that is the first thing people trip over.
What it helps with
- decide whether state belongs in the URL, the database or React
- pass page data from the controller without
useEffectandfetch - build a form that handles CSRF, error mapping and file uploads
- refresh a subset of props instead of reloading the whole page
- tell when logic belongs in a separate API endpoint
Example prompts for the agent
- Add an orders index page in Inertia Rails and pass the data as controller props
- Build a client creation form with the Form component and server-side validation
- I need city autocomplete on every keystroke — decide whether that goes through Inertia or a separate endpoint
How to install inertia-rails-architecture
1Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill inertia-rails-architecture -a claude-code2Manually: put the files in place
~/.claude/skills/inertia-rails-architecture/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/inertia-rails-architecture/
1Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill inertia-rails-architecture -a codex2Manually: put the files in place
.agents/skills/inertia-rails-architecture/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill inertia-rails-architecture -a cursor2Manually: put the files in place
.cursor/skills/inertia-rails-architecture/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install inertia-rails/skills inertia-rails-architecture2Manually: put the files in place
.github/skills/inertia-rails-architecture/SKILL.md1Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill inertia-rails-architecture -a gemini-cli2Platform CLI
gemini skills install https://github.com/inertia-rails/skills --path skills/inertia-rails-architectureManually: put the files in place
.devin/skills/inertia-rails-architecture/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add inertia-rails/skills --skill inertia-rails-architecture -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about inertia-rails-architecture
How do I install inertia-rails-architecture in Claude?
Run npx skills add inertia-rails/skills --skill inertia-rails-architecture -a claude-code. The skill lands at ~/.claude/skills/inertia-rails-architecture/SKILL.md.
What does inertia-rails-architecture do?
It is the entry skill for Inertia Rails + React. It explains that the server owns routing, data and auth, then points you to the companion skills for the task at hand.
When should I use a plain API endpoint instead of Inertia?
When the consumer is not a browser, when the dataset is too large to load as a prop, or when the response is binary — a PDF or CSV export, a file download.
Source text
SKILL.md · MIT
---
name: inertia-rails-architecture
description: >-
Server-driven architecture patterns for Inertia Rails + React. Load this FIRST when building any Inertia page
or feature — it routes to the right skill. Decision matrix for data loading, forms, navigation, state management.
NEVER useEffect+fetch, NEVER redirect_to for external URLs (use inertia_location), NEVER react-hook-form
(use Form component). MUST invoke when adding pages, models with views, CRUD, or displaying data in an
Inertia Rails app. ALWAYS `render inertia: { key: value }` to pass data — `@ivars` are NOT auto-passed as props.
---
# Inertia Rails Architecture
Server-driven architecture for Rails + Inertia.js + React when building pages,
forms, navigation, or data refresh. Inertia is NOT a traditional SPA — the
server owns routing, data, and auth. React handles rendering only.
## The Core Mental Model
The server is the source of truth. React receives data as props and renders UI.
There is no client-side router, no global state store, no API layer.
**Before building any feature, ask:**
- **Where does the data come from?** → If server: controller prop. If user interaction: `useState`.
- **Who owns this state?** → If it's in the URL or DB: server owns it (use props). If it's ephemeral UI: React owns it.
- **Am I reaching for a React/SPA pattern?** → Check the decision matrix below first — Inertia likely has a server-driven equivalent.
## Decision Matrix
| Need | Solution | NOT This |
|------|----------|----------|
| Page data from server | Controller props | useEffect + fetch |
| Global data (auth, config) | `inertia_share` + `usePage()` | React Context / Redux |
| Flash messages / toasts | Rails `flash` + `usePage().flash` | inertia_share / React state |
| Form submission | `<Form>` component | fetch/axios + useState |
| Navigate between pages | `<Link>` / `router.visit` | react-router / window.location |
| Refresh specific data | `router.reload({ only: [...] })` | React Query / SWR |
| Expensive server data | `InertiaRails.defer` | useEffect + loading state |
| Infinite scroll | `InertiaRails.scroll` + `<InfiniteScroll>` | Client-side pagination |
| Stable reference data | `InertiaRails.once` | Cache in React state |
| Real-time updates (core) | ActionCable + `router.reload` | Polling with setInterval |
| Simple polling (MVP/prototyping) | `usePoll` (auto-throttles in background tabs) | setInterval + router.reload |
| URL-driven UI state (dialogs, tabs) | Controller reads `params` → prop, `router.get` to update | useEffect + window.location |
| Ephemeral UI state | `useState` / `useReducer` | Server props |
| External API calls | Dedicated API endpoint | Mixing with Inertia props |
## Rules (by impact)
| # | Impact | Rule | WHY |
|---|--------|------|-----|
| 1 | CRITICAL | Never useEffect+fetch for page data | Inertia re-renders the full component on navigation; a useEffect fetch creates a second data lifecycle that drifts from props and causes stale UI |
| 2 | CRITICAL | Never check auth client-side | Auth state in React can be spoofed; server-side checks are the only real gate. Client-side "guards" give false security |
| 3 | CRITICAL | Use `<Form>`, not fetch/axios | `<Form>` handles CSRF, redirect-following, error mapping, file detection, and history state — fetch duplicates or breaks all of this |
| 4 | HIGH | Use `<Link>` and `router`, not `<a>` or window.location | `<a>` triggers a full page reload, destroying all React state and layout persistence |
| 5 | HIGH | Use partial reloads, not React Query/SWR | React Query adds a second cache layer that conflicts with Inertia's page-based caching and versioning |
| 5b | HIGH | Use `usePoll` only for MVPs; prefer ActionCable for production real-time | `usePoll` is convenient but wastes bandwidth — every interval hits the server even when nothing changed. ActionCable pushes only on actual changes |
| 6 | HIGH | Use `inertia_share` for global data, not React Context | Context re-renders consumers on every change; shared props are per-request and integrated with partial reloads |
| 7 | HIGH | Use Rails flash for notifications, not shared props | Flash auto-clears after one response; shared props persist until explicitly changed, causing stale toasts |
| 8 | MEDIUM | Use deferred/optional props for expensive queries | Blocks initial render otherwise — user sees blank page until slow query finishes |
| 9 | MEDIUM | Use persistent layouts for state preservation | Without persistent layout, layout remounts on every navigation — scroll position, audio playback, and component state are lost |
| 10 | MEDIUM | Keep React components as renderers, not data fetchers | Mixing data-fetching into components makes them untestable and breaks Inertia's server-driven model |
## Skill Map
Common workflows span multiple skills — load all listed for complete coverage:
| Workflow | Load these skills |
|----------|-------------------|
| New page with props | `inertia-rails-controllers` + `inertia-rails-pages` + `inertia-rails-typescript` |
| Form with validation | `inertia-rails-forms` + `inertia-rails-controllers` |
| shadcn form inputs | `inertia-rails-forms` + `shadcn-inertia` |
| Flash toasts | `inertia-rails-controllers` + `inertia-rails-pages` + `shadcn-inertia` |
| Deferred/lazy data | `inertia-rails-controllers` + `inertia-rails-pages` |
| URL-driven dialog/tabs | `inertia-rails-controllers` + `inertia-rails-pages` |
| Alba serialization | `alba-inertia` + `inertia-rails-typescript` |
| Testing controllers | `inertia-rails-testing` + `inertia-rails-controllers` |
## References
**MANDATORY — READ ENTIRE FILE** before building a new Inertia page or feature:
[`references/AGENTS.md`](references/AGENTS.md) (~430 lines) — full-stack examples for
each pattern in the decision matrix above.
**MANDATORY — READ ENTIRE FILE** when unsure which Inertia pattern to use:
[`references/decision-trees.md`](references/decision-trees.md) (~70 lines) — flowcharts
for choosing between prop types, navigation methods, and data strategies.
**Do NOT load** references for quick questions about a single pattern already
covered in the decision matrix above.
## When You DO Need a Separate API
Not everything belongs in Inertia's request cycle. Use a traditional API endpoint when:
| Signal | Why | Example |
|--------|-----|---------|
| Non-browser consumer | Inertia's JSON envelope (component, props, url, version) is designed for the frontend adapter — other consumers can't use it | Mobile API, CLI tools, payment webhooks |
| Large-dataset search | Dataset is too big to load as a prop; each input needs per-keystroke server filtering. Use raw fetch for the search, let Inertia handle post-selection side effects via props. | City/address autocomplete, postal code lookup |
| Binary/streaming response | Inertia can only deliver JSON props. Use a separate route with a standard download response. | PDF/CSV export, file downloads |
© inertia-rails · MIT · inertia-rails/skills