creating-inertia-pages
by moonpixels
Laravel skeleton application template with Inertia React.
SKILL.md
name: creating-inertia-pages description: Create Inertia.js page components with TypeScript, forms, and navigation. Use when creating pages, implementing routes, handling forms, or when user mentions pages, Inertia pages, router, or page navigation.
Create Inertia.js Pages
Create Inertia.js page components with TypeScript, forms, and navigation. Pages receive props from Laravel controllers, handle form submissions via the useForm hook, and navigate using the Inertia router without full page reloads.
File Structure
Pages are organized by feature or domain:
resources/js/pages/{feature}/{name}.tsx
Examples:
resources/js/pages/auth/login.tsxresources/js/pages/auth/register.tsxresources/js/pages/dashboard.tsxresources/js/pages/profile/edit.tsx
Core Conventions
1. Page Component Structure
Pages must use default exports (unlike other components):
import { AuthenticatedLayout } from '@/layouts/authenticated-layout'
import { useTranslation } from 'react-i18next'
export default function Dashboard() {
const { t } = useTranslation()
return (
<AuthenticatedLayout title={t('dashboard')}>
<h1 className="text-2xl font-bold">{t('welcome')}</h1>
<DashboardContent />
</AuthenticatedLayout>
)
}
Key Requirements:
- Use function declarations for pages (NOT arrow functions assigned to const)
- Use default export for page components (exception to named export rule)
- Wrap content in a Layout component
- Use
useTranslation()for i18n - Type props with
PagePropsfrom Inertia
2. Accessing Inertia Props
Use usePage() to access props passed from Laravel:
import type { PageProps } from '@/types'
import { usePage } from '@inertiajs/react'
interface DashboardProps extends PageProps {
stats: {
users: number
posts: number
}
}
export default function Dashboard() {
const { stats } = usePage<DashboardProps>().props
return (
<div>
<p>Users: {stats.users}</p>
<p>Posts: {stats.posts}</p>
</div>
)
}
Shared props (available on all pages):
export default function Header() {
const { user, supportedLocales } = usePage().props
return <header>{user && <span>Welcome, {user.name}</span>}</header>
}
3. Form Handling with Inertia
Use router for form submissions:
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useFormValidation } from '@/hooks/use-form-validation'
import { zodResolver } from '@hookform/resolvers/zod'
import { router } from '@inertiajs/react'
import { useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { z } from 'zod'
const formSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
})
export default function Login() {
const { t } = useTranslation()
const { setFormServerErrors } = useFormValidation()
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
email: '',
password: '',
},
})
async function onSubmit(values: z.infer<typeof formSchema>) {
await new Promise<void>((resolve) =>
router.post(route('login'), values, {
onError: (errors) => {
setFormServerErrors(form, errors)
},
onFinish: () => resolve(),
})
)
}
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
<Input {...form.register('email')} />
<Input {...form.register('password')} type="password" />
<Button type="submit" loading={form.formState.isSubmitting}>
{t('logIn')}
</Button>
</form>
)
}
4. Navigation with Inertia Router
Using router.get():
import { router } from '@inertiajs/react'
export function NavigationButton() {
const navigate = () => {
router.get(route('dashboard'))
}
return <Button onClick={navigate}>Go to Dashboard</Button>
}
Using Link component:
import { Link } from '@inertiajs/react'
export function Navigation() {
return (
<nav>
<Link href={route('dashboard')}>Dashboard</Link>
<Link href={route('profile.edit')}>Profile</Link>
</nav>
)
}
Examples
Example 1: Simple Page
import { GuestLayout } from '@/layouts/guest-layout'
import { useTranslation } from 'react-i18next'
export default function Welcome() {
const { t } = useTranslation()
return (
<GuestLayout title={t('welcome')}>
<h1 className="text-4xl font-bold">{t('welcome')}</h1>
<p className="mt-4 text-lg">{t('welcomeDescription')}</p>
</GuestLayout>
)
}
Example 2: Page with Props
import { AuthenticatedLayout } from '@/layouts/authenticated-layout'
import type { User } from '@/types/models'
import type { PageProps, PaginatedData } from '@/types'
import { usePage } from '@inertiajs/react'
interface UsersIndexProps extends PageProps {
users: PaginatedData<User>
}
export default function UsersIndex() {
const { users } = usePage<UsersIndexProps>().props
return (
<AuthenticatedLayout title="Users">
<h1 className="text-2xl font-bold">Users</h1>
<div className="mt-6 space-y-4">
{users.data.map((user) => (
<UserCard key={user.id} user={user} />
))}
</div>
<Pagination meta={users.meta} />
</AuthenticatedLayout>
)
}
Example 3: Form Page
import { AuthForm, AuthFormFooter } from '@/components/auth-form'
import { Text } from '@/components/text'
import { TextLink } from '@/components/text-link'
import { Button } from '@/components/ui/button'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { useFormValidation } from '@/hooks/use-form-validation'
import { GuestLayout } from '@/layouts/guest-layout'
import { zodResolver } from '@hookform/resolvers/zod'
import { router } from '@inertiajs/react'
import { useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { z } from 'zod'
const formSchema = z.object({
name: z.string(),
email: z.string().email(),
password: z.string().min(8),
password_confirmation: z.string().min(8),
})
export default function Register() {
const { t } = useTranslation()
const { setFormServerErrors, passwordConfirmationMessage } =
useFormValidation()
formSchema.refine(
(data) => data.password === data.password_confirmation,
passwordConfirmationMessage
)
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
name: '',
email: '',
password: '',
password_confirmation: '',
},
})
async function onSubmit(values: z.infer<typeof formSchema>) {
await new Promise<void>((resolve) =>
router.post(
route('register'),
{
...values,
language: navigator.language,
},
{
onError: (errors) => {
setFormServerErrors(form, errors)
},
onFinish: () => resolve(),
}
)
)
}
return (
<GuestLayout title={t('registerNewAccount')}>
<AuthForm
description={t('registerNewAccountDescription')}
title={t('registerNewAccount')}
>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input autoComplete="name" required {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>{t('email')}</FormLabel>
<FormControl>
<Input
autoComplete="username"
inputMode="email"
required
type="email"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
loading={form.formState.isSubmitting}
className="w-full"
type="submit"
>
{t('register')}
</Button>
</form>
</Form>
<AuthFormFooter>
<Text size="sm" variant="muted">
{t('alreadyHaveAccount') + ' '}
<TextLink href={route('login')}>{t('logIn')}</TextLink>
</Text>
</AuthFormFooter>
</AuthForm>
</GuestLayout>
)
}
Anti-Patterns
❌ Don't Do This
// Don't use arrow functions assigned to const for pages
const Dashboard = () => {
// ❌ Use function declaration
return <div>Content</div>
}
export default Dashboard
// Don't use named exports for pages
export function Dashboard() {} // ❌ Must use default export
// Don't forget to wrap in layout
export default function Dashboard() {
return <div>Content</div> // ❌ Missing layout
}
// Don't use untyped props
export default function Users() {
const { users } = usePage().props // ❌ Missing type
}
// Don't navigate with window.location
export function Button() {
const navigate = () => {
window.location.href = '/dashboard' // ❌ Use router.get()
}
}
// Don't forget error handling
async function onSubmit(values) {
router.post(route('login'), values) // ❌ Missing onError
}
✅ Do This Instead
// Use default exports
export default function Dashboard() {}
// Always wrap in layout
export default function Dashboard() {
return (
<AuthenticatedLayout title="Dashboard">
<div>Content</div>
</AuthenticatedLayout>
)
}
// Type props with PageProps
interface UsersProps extends PageProps {
users: User[]
}
export default function Users() {
const { users } = usePage<UsersProps>().props
}
// Use Inertia router
export function Button() {
const navigate = () => {
router.get(route('dashboard'))
}
}
// Handle errors
async function onSubmit(values: FormValues) {
await new Promise<void>((resolve) =>
router.post(route('login'), values, {
onError: (errors) => {
setFormServerErrors(form, errors)
},
onFinish: () => resolve(),
})
)
}
Router Methods
Navigation:
router.get(route('dashboard'))
router.visit(route('profile.edit'))
Form submissions:
router.post(route('login'), data, { onError, onFinish })
router.put(route('profile.update'), data)
router.patch(route('user.update', userId), data)
router.delete(route('user.destroy', userId))
Reload current page:
router.reload()
router.reload({ only: ['users'] }) // Only reload 'users' prop
Quality Standards
- All pages must use default exports
- Wrap all pages in layout components
- Type page props with
PageProps<T> - Use
routerfor navigation and form submissions - Handle form errors with
onErrorcallback - Use
route()helper for all URLs - Test pages with Pest browser tests
- Ensure responsive design and accessibility
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です