inertia-rails-typescript
npx skills add https://github.com/inertia-rails/skills --skill inertia-rails-typescript
Agent 安装分布
Skill 文档
Inertia Rails TypeScript Setup
Type-safe shared props, flash, and errors using InertiaConfig module augmentation.
Works identically across React, Vue, and Svelte â the globals.d.ts and InertiaConfig setup is the same for all frameworks.
Before adding TypeScript types, ask:
- Shared props (auth, flash)? â Update
SharedProps/FlashDatainindex.tsâ InertiaConfig inglobals.d.tspropagates them globally viausePage() - Page-specific props? â
type Props = { ... }in the page file only â never include shared props here
InertiaConfig Module Augmentation
Define shared props type ONCE globally â never in individual page components.
InertiaConfig property names are EXACT â do not rename them:
sharedPageProps(NOT sharedProps)flashDataType(NOT flashProps, NOT flashData)errorValueType(NOT errorBag, NOT errorType)
// app/frontend/types/globals.d.ts
import type { FlashData, SharedProps } from '@/types'
declare module '@inertiajs/core' {
export interface InertiaConfig {
sharedPageProps: SharedProps // EXACT name â auto-typed for usePage().props
flashDataType: FlashData // EXACT name â auto-typed for usePage().flash
errorValueType: string[] // EXACT name â errors are arrays of strings
}
}
// app/frontend/types/index.ts
export interface FlashData {
notice?: string
alert?: string
}
export interface SharedProps {
auth: { user?: { id: number; name: string; email: string } }
}
Convention: Use auth: { user: ... } as the shared props key â this matches the
Rails inertia_share community convention ({ auth: { user: current_user } }).
The auth namespace separates authentication data from page props, preventing
collisions when a page has its own user prop. Do NOT use current_user: or
user: as top-level keys â they collide with page-specific props and break the
convention that other Inertia skills and examples assume.
BAD vs GOOD Patterns
// BAD â passing shared props as generics:
// usePage<{ users: User[], auth: AuthData, flash: FlashData }>()
// BAD â extending a SharedProps interface into page props:
// interface Props extends SharedData { users: User[] }
// BAD â declaring PageProps interface:
// interface PageProps { auth: AuthData; flash: FlashData }
// BAD â using current_user or user as top-level shared key:
// interface SharedProps { current_user: User }
// BAD â destructuring auth directly from usePage() (TS2339: 'auth' does not exist on Page):
// const { auth } = usePage()
// usePage() returns a Page object with { props, flash, component, url, ... }
// auth lives inside props, not on the Page itself
// BAD â duplicating InertiaConfig in index.ts (it belongs in globals.d.ts):
// declare module '@inertiajs/core' { ... } â in index.ts
// GOOD â props from usePage().props, flash from usePage().flash:
const { props, flash } = usePage()
// props.auth is typed (from SharedProps via InertiaConfig)
// flash.notice is typed (from FlashData via InertiaConfig)
Important: globals.d.ts configures InertiaConfig ONCE. When adding a new shared
prop, only update index.ts â do NOT touch globals.d.ts:
// BEFORE â app/frontend/types/index.ts
export interface SharedProps {
auth: { user?: { id: number; name: string; email: string } }
}
// AFTER â add the new key here, NOT in globals.d.ts
export interface SharedProps {
auth: { user?: { id: number; name: string; email: string } }
notifications: { unread_count: number }
}
InertiaConfig in globals.d.ts references SharedProps by name â it picks up the
change automatically. Adding a second declare module '@inertiajs/core' causes conflicts.
Page-Specific Props
Page components type ONLY their own props. Shared props (like auth) and flash come from InertiaConfig automatically.
type vs interface for page props (React-specific)
This constraint applies to React only. Vue’s defineProps<T>() and Svelte’s
$props() do not use usePage<T>() generics, so interface works fine there.
usePage<T>() requires T to have an index signature. type aliases have one
implicitly; interface declarations do not. Using interface with usePage
causes TS2344 at compile time.
| Pattern | Works with usePage<T>()? |
Notes |
|---|---|---|
type Props = { users: User[] } |
Yes | Preferred â just works |
interface Props { users: User[] } |
No â TS2344 | Missing index signature |
usePage<Required<Props>>() |
Yes | Wraps interface to add index signature |
// React
type Props = {
users: User[] // page-specific only
// auth is NOT here â it comes from InertiaConfig globally
}
export default function Index({ users }: Props) {
// Access shared props separately:
const { props, flash } = usePage()
// props.auth is typed via InertiaConfig
// flash.notice is typed via InertiaConfig
return <UserList users={users} />
}
Accessing shared props in Vue and Svelte
Vue and Svelte use different patterns to access shared props, but InertiaConfig typing works the same way.
<!-- Vue 3 â usePage() returns reactive object; use computed() for derived values -->
<script setup lang="ts">
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
const page = usePage()
const userName = computed(() => page.props.auth.user?.name) // typed via InertiaConfig
</script>
<!-- Svelte â page store from @inertiajs/svelte -->
<script lang="ts">
import { page } from '@inertiajs/svelte'
// $page.props.auth is typed via InertiaConfig
// $page.flash.notice is typed via InertiaConfig
</script>
Common TypeScript Errors
| Error | Cause | Fix |
|---|---|---|
TS2344 on usePage<Props>() |
interface lacks index signature |
Use type Props = { ... } instead of interface, or wrap: usePage<Required<Props>>() |
TS2339 'auth' does not exist on type Page |
Destructuring auth from usePage() directly |
usePage() returns { props, flash, ... } â use usePage().props.auth, not usePage().auth |
TS2339 'flash' does not exist on type |
Accessing usePage().props.flash |
Flash is top-level: usePage().flash, NOT usePage().props.flash |
| Shared props untyped | Missing InertiaConfig | Add globals.d.ts with module augmentation (see above) |
| InertiaConfig not taking effect | Declaration in wrong file | Must be in a .d.ts file (e.g., globals.d.ts), not in .ts â TypeScript ignores declare module in regular .ts files that have imports/exports |
| Types correct but IDE shows errors | globals.d.ts not included |
Verify tsconfig.app.json includes the types directory in include array |
Typelizer Integration
If using the typelizer gem (see alba-inertia skill), SharedProps are auto-generated
from your serializer â do NOT manually write the SharedProps interface in index.ts.
You only write globals.d.ts once (the InertiaConfig augmentation). When you add a new
attribute to SharedPropsResource, Typelizer regenerates index.ts and the types
propagate via InertiaConfig â no manual type updates needed.
Related Skills
- Shared props setup â
inertia-rails-controllers(inertia_share) - Flash config â
inertia-rails-controllers(flash_keys) - Auto-generated types â
alba-inertia(Typelizer + Alba resources) - Page component props â
inertia-rails-pages(type Props pattern)