laravel-inertia-react
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLaravel + Inertia.js + React
Laravel + Inertia.js + React
Comprehensive patterns for building modern monolithic applications with Laravel, Inertia.js, and React. Contains 30+ rules for seamless full-stack development.
这是使用Laravel、Inertia.js和React构建现代单体应用的综合模式指南,包含30+条实现无缝全栈开发的规则。
When to Apply
适用场景
Reference these guidelines when:
- Creating Inertia page components
- Handling forms with useForm hook
- Managing shared data and authentication
- Implementing persistent layouts
- Navigating between pages
在以下场景中可参考本指南:
- 构建Inertia页面组件
- 使用useForm钩子处理表单
- 管理共享数据与认证信息
- 实现持久化布局
- 页面间导航
Rule Categories by Priority
按优先级划分的规则类别
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Page Components | CRITICAL | |
| 2 | Forms & Validation | CRITICAL | |
| 3 | Navigation & Links | HIGH | |
| 4 | Shared Data | HIGH | |
| 5 | Layouts | MEDIUM | |
| 6 | File Uploads | MEDIUM | |
| 7 | Advanced Patterns | LOW | |
| 优先级 | 类别 | 影响程度 | 前缀 |
|---|---|---|---|
| 1 | 页面组件 | 关键 | |
| 2 | 表单与验证 | 关键 | |
| 3 | 导航与链接 | 高 | |
| 4 | 共享数据 | 高 | |
| 5 | 布局 | 中 | |
| 6 | 文件上传 | 中 | |
| 7 | 高级模式 | 低 | |
Quick Reference
快速参考
1. Page Components (CRITICAL)
1. 页面组件(关键)
- - Type page props from Laravel
page-props-typing - - Standard page component pattern
page-component-structure - - Title and meta tags with Head
page-head-management - - Assign layouts to pages
page-default-layout
- - 为来自Laravel的页面props添加类型定义
page-props-typing - - 标准页面组件模式
page-component-structure - - 使用Head组件管理标题与元标签
page-head-management - - 为页面分配布局
page-default-layout
2. Forms & Validation (CRITICAL)
2. 表单与验证(关键)
- - Basic useForm usage
form-useform-basic - - Display Laravel validation errors
form-validation-errors - - Handle form submission state
form-processing-state - - Reset vs preserve form data
form-reset-preserve - - Handle nested form data
form-nested-data - - Transform data before submit
form-transform
- - useForm基础用法
form-useform-basic - - 展示Laravel验证错误信息
form-validation-errors - - 处理表单提交状态
form-processing-state - - 表单数据重置与保留的处理
form-reset-preserve - - 处理嵌套表单数据
form-nested-data - - 提交前转换数据
form-transform
3. Navigation & Links (HIGH)
3. 导航与链接(高)
- - Use Link for navigation
nav-link-component - - Preserve scroll and state
nav-preserve-state - - Reload only what changed
nav-partial-reloads - - Replace vs push history
nav-replace-history
- - 使用Link组件实现导航
nav-link-component - - 保留滚动位置与页面状态
nav-preserve-state - - 仅重新加载变更内容
nav-partial-reloads - - 替换历史记录与新增历史记录的区别
nav-replace-history
4. Shared Data (HIGH)
4. 共享数据(高)
- - Access authenticated user
shared-auth-user - - Handle flash messages
shared-flash-messages - - Access global props
shared-global-props - - Type shared data
shared-typescript
- - 访问已认证用户信息
shared-auth-user - - 处理闪存消息
shared-flash-messages - - 访问全局props
shared-global-props - - 为共享数据添加类型定义
shared-typescript
5. Layouts (MEDIUM)
5. 布局(中)
- - Persistent layouts pattern
layout-persistent - - Nested layouts
layout-nested - - Default layout assignment
layout-default - - Conditional layouts
layout-conditional
- - 持久化布局模式
layout-persistent - - 嵌套布局
layout-nested - - 默认布局分配
layout-default - - 条件式布局
layout-conditional
6. File Uploads (MEDIUM)
6. 文件上传(中)
- - Basic file upload
upload-basic - - Upload progress tracking
upload-progress - - Multiple file uploads
upload-multiple
- - 基础文件上传
upload-basic - - 上传进度追踪
upload-progress - - 多文件上传
upload-multiple
7. Advanced Patterns (LOW)
7. 高级模式(低)
- - Real-time polling
advanced-polling - - Prefetch pages
advanced-prefetch - - Modal as pages
advanced-modal-pages - - Infinite scrolling
advanced-infinite-scroll
- - 实时轮询
advanced-polling - - 页面预加载
advanced-prefetch - - 将模态框作为页面实现
advanced-modal-pages - - 无限滚动
advanced-infinite-scroll
Essential Patterns
核心模式示例
Page Component with TypeScript
带TypeScript的页面组件
tsx
// resources/js/Pages/Posts/Index.tsx
import { Head, Link } from '@inertiajs/react'
interface Post {
id: number
title: string
excerpt: string
created_at: string
author: {
id: number
name: string
}
}
interface Props {
posts: {
data: Post[]
links: { url: string | null; label: string; active: boolean }[]
}
filters: {
search?: string
}
}
export default function Index({ posts, filters }: Props) {
return (
<>
<Head title="Posts" />
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold mb-6">Posts</h1>
<div className="space-y-4">
{posts.data.map((post) => (
<article key={post.id} className="p-4 bg-white rounded-lg shadow">
<Link href={route('posts.show', post.id)}>
<h2 className="text-xl font-semibold hover:text-blue-600">
{post.title}
</h2>
</Link>
<p className="text-gray-600 mt-2">{post.excerpt}</p>
<p className="text-sm text-gray-400 mt-2">
By {post.author.name}
</p>
</article>
))}
</div>
</div>
</>
)
}tsx
// resources/js/Pages/Posts/Index.tsx
import { Head, Link } from '@inertiajs/react'
interface Post {
id: number
title: string
excerpt: string
created_at: string
author: {
id: number
name: string
}
}
interface Props {
posts: {
data: Post[]
links: { url: string | null; label: string; active: boolean }[]
}
filters: {
search?: string
}
}
export default function Index({ posts, filters }: Props) {
return (
<>
<Head title="Posts" />
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold mb-6">Posts</h1>
<div className="space-y-4">
{posts.data.map((post) => (
<article key={post.id} className="p-4 bg-white rounded-lg shadow">
<Link href={route('posts.show', post.id)}>
<h2 className="text-xl font-semibold hover:text-blue-600">
{post.title}
</h2>
</Link>
<p className="text-gray-600 mt-2">{post.excerpt}</p>
<p className="text-sm text-gray-400 mt-2">
By {post.author.name}
</p>
</article>
))}
</div>
</div>
</>
)
}Form with useForm
使用useForm的表单
tsx
// resources/js/Pages/Posts/Create.tsx
import { Head, useForm, Link } from '@inertiajs/react'
import { FormEvent } from 'react'
interface Category {
id: number
name: string
}
interface Props {
categories: Category[]
}
export default function Create({ categories }: Props) {
const { data, setData, post, processing, errors, reset } = useForm({
title: '',
body: '',
category_id: '',
})
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
post(route('posts.store'), {
onSuccess: () => reset(),
})
}
return (
<>
<Head title="Create Post" />
<form onSubmit={handleSubmit} className="max-w-2xl mx-auto py-8">
<div className="mb-4">
<label htmlFor="title" className="block font-medium mb-1">
Title
</label>
<input
id="title"
type="text"
value={data.title}
onChange={(e) => setData('title', e.target.value)}
className="w-full border rounded px-3 py-2"
/>
{errors.title && (
<p className="text-red-500 text-sm mt-1">{errors.title}</p>
)}
</div>
<div className="mb-4">
<label htmlFor="category" className="block font-medium mb-1">
Category
</label>
<select
id="category"
value={data.category_id}
onChange={(e) => setData('category_id', e.target.value)}
className="w-full border rounded px-3 py-2"
>
<option value="">Select a category</option>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
{errors.category_id && (
<p className="text-red-500 text-sm mt-1">{errors.category_id}</p>
)}
</div>
<div className="mb-4">
<label htmlFor="body" className="block font-medium mb-1">
Content
</label>
<textarea
id="body"
value={data.body}
onChange={(e) => setData('body', e.target.value)}
rows={10}
className="w-full border rounded px-3 py-2"
/>
{errors.body && (
<p className="text-red-500 text-sm mt-1">{errors.body}</p>
)}
</div>
<div className="flex gap-4">
<button
type="submit"
disabled={processing}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{processing ? 'Creating...' : 'Create Post'}
</button>
<Link
href={route('posts.index')}
className="px-4 py-2 border rounded"
>
Cancel
</Link>
</div>
</form>
</>
)
}tsx
// resources/js/Pages/Posts/Create.tsx
import { Head, useForm, Link } from '@inertiajs/react'
import { FormEvent } from 'react'
interface Category {
id: number
name: string
}
interface Props {
categories: Category[]
}
export default function Create({ categories }: Props) {
const { data, setData, post, processing, errors, reset } = useForm({
title: '',
body: '',
category_id: '',
})
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
post(route('posts.store'), {
onSuccess: () => reset(),
})
}
return (
<>
<Head title="Create Post" />
<form onSubmit={handleSubmit} className="max-w-2xl mx-auto py-8">
<div className="mb-4">
<label htmlFor="title" className="block font-medium mb-1">
Title
</label>
<input
id="title"
type="text"
value={data.title}
onChange={(e) => setData('title', e.target.value)}
className="w-full border rounded px-3 py-2"
/>
{errors.title && (
<p className="text-red-500 text-sm mt-1">{errors.title}</p>
)}
</div>
<div className="mb-4">
<label htmlFor="category" className="block font-medium mb-1">
Category
</label>
<select
id="category"
value={data.category_id}
onChange={(e) => setData('category_id', e.target.value)}
className="w-full border rounded px-3 py-2"
>
<option value="">Select a category</option>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
{errors.category_id && (
<p className="text-red-500 text-sm mt-1">{errors.category_id}</p>
)}
</div>
<div className="mb-4">
<label htmlFor="body" className="block font-medium mb-1">
Content
</label>
<textarea
id="body"
value={data.body}
onChange={(e) => setData('body', e.target.value)}
rows={10}
className="w-full border rounded px-3 py-2"
/>
{errors.body && (
<p className="text-red-500 text-sm mt-1">{errors.body}</p>
)}
</div>
<div className="flex gap-4">
<button
type="submit"
disabled={processing}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{processing ? 'Creating...' : 'Create Post'}
</button>
<Link
href={route('posts.index')}
className="px-4 py-2 border rounded"
>
Cancel
</Link>
</div>
</form>
</>
)
}Persistent Layout
持久化布局
tsx
// resources/js/Layouts/AppLayout.tsx
import { Link, usePage } from '@inertiajs/react'
import { ReactNode } from 'react'
interface Props {
children: ReactNode
}
export default function AppLayout({ children }: Props) {
const { auth } = usePage().props as { auth: { user: { name: string } } }
return (
<div className="min-h-screen bg-gray-100">
<nav className="bg-white shadow">
<div className="container mx-auto px-4 py-3 flex justify-between">
<Link href="/" className="font-bold">
My App
</Link>
<span>Welcome, {auth.user.name}</span>
</div>
</nav>
<main className="container mx-auto px-4 py-8">
{children}
</main>
</div>
)
}
// resources/js/Pages/Dashboard.tsx
import AppLayout from '@/Layouts/AppLayout'
export default function Dashboard() {
return <h1>Dashboard</h1>
}
// Assign persistent layout
Dashboard.layout = (page: ReactNode) => <AppLayout>{page}</AppLayout>tsx
// resources/js/Layouts/AppLayout.tsx
import { Link, usePage } from '@inertiajs/react'
import { ReactNode } from 'react'
interface Props {
children: ReactNode
}
export default function AppLayout({ children }: Props) {
const { auth } = usePage().props as { auth: { user: { name: string } } }
return (
<div className="min-h-screen bg-gray-100">
<nav className="bg-white shadow">
<div className="container mx-auto px-4 py-3 flex justify-between">
<Link href="/" className="font-bold">
My App
</Link>
<span>Welcome, {auth.user.name}</span>
</div>
</nav>
<main className="container mx-auto px-4 py-8">
{children}
</main>
</div>
)
}
// resources/js/Pages/Dashboard.tsx
import AppLayout from '@/Layouts/AppLayout'
export default function Dashboard() {
return <h1>Dashboard</h1>
}
// Assign persistent layout
Dashboard.layout = (page: ReactNode) => <AppLayout>{page}</AppLayout>Laravel Controller
Laravel控制器
php
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
class PostController extends Controller
{
public function index(): Response
{
return Inertia::render('Posts/Index', [
'posts' => Post::with('author:id,name')
->latest()
->paginate(10),
'filters' => request()->only('search'),
]);
}
public function create(): Response
{
return Inertia::render('Posts/Create', [
'categories' => Category::all(['id', 'name']),
]);
}
public function store(StorePostRequest $request): RedirectResponse
{
$post = Post::create([
...$request->validated(),
'user_id' => auth()->id(),
]);
return redirect()
->route('posts.show', $post)
->with('success', 'Post created successfully.');
}
public function show(Post $post): Response
{
return Inertia::render('Posts/Show', [
'post' => $post->load('author', 'category'),
]);
}
}php
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
class PostController extends Controller
{
public function index(): Response
{
return Inertia::render('Posts/Index', [
'posts' => Post::with('author:id,name')
->latest()
->paginate(10),
'filters' => request()->only('search'),
]);
}
public function create(): Response
{
return Inertia::render('Posts/Create', [
'categories' => Category::all(['id', 'name']),
]);
}
public function store(StorePostRequest $request): RedirectResponse
{
$post = Post::create([
...$request->validated(),
'user_id' => auth()->id(),
]);
return redirect()
->route('posts.show', $post)
->with('success', 'Post created successfully.');
}
public function show(Post $post): Response
{
return Inertia::render('Posts/Show', [
'post' => $post->load('author', 'category'),
]);
}
}Shared Data (HandleInertiaRequests)
共享数据配置(HandleInertiaRequests)
php
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'auth' => [
'user' => $request->user() ? [
'id' => $request->user()->id,
'name' => $request->user()->name,
'email' => $request->user()->email,
] : null,
],
'flash' => [
'success' => $request->session()->get('success'),
'error' => $request->session()->get('error'),
],
]);
}
}php
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'auth' => [
'user' => $request->user() ? [
'id' => $request->user()->id,
'name' => $request->user()->name,
'email' => $request->user()->email,
] : null,
],
'flash' => [
'success' => $request->session()->get('success'),
'error' => $request->session()->get('error'),
],
]);
}
}Flash Messages Component
闪存消息组件
tsx
// resources/js/Components/FlashMessages.tsx
import { usePage } from '@inertiajs/react'
import { useEffect, useState } from 'react'
export default function FlashMessages() {
const { flash } = usePage().props as {
flash: { success?: string; error?: string }
}
const [visible, setVisible] = useState(false)
useEffect(() => {
if (flash.success || flash.error) {
setVisible(true)
const timer = setTimeout(() => setVisible(false), 3000)
return () => clearTimeout(timer)
}
}, [flash])
if (!visible) return null
return (
<div className="fixed top-4 right-4 z-50">
{flash.success && (
<div className="bg-green-500 text-white px-4 py-2 rounded shadow">
{flash.success}
</div>
)}
{flash.error && (
<div className="bg-red-500 text-white px-4 py-2 rounded shadow">
{flash.error}
</div>
)}
</div>
)
}tsx
// resources/js/Components/FlashMessages.tsx
import { usePage } from '@inertiajs/react'
import { useEffect, useState } from 'react'
export default function FlashMessages() {
const { flash } = usePage().props as {
flash: { success?: string; error?: string }
}
const [visible, setVisible] = useState(false)
useEffect(() => {
if (flash.success || flash.error) {
setVisible(true)
const timer = setTimeout(() => setVisible(false), 3000)
return () => clearTimeout(timer)
}
}, [flash])
if (!visible) return null
return (
<div className="fixed top-4 right-4 z-50">
{flash.success && (
<div className="bg-green-500 text-white px-4 py-2 rounded shadow">
{flash.success}
</div>
)}
{flash.error && (
<div className="bg-red-500 text-white px-4 py-2 rounded shadow">
{flash.error}
</div>
)}
</div>
)
}How to Use
使用方法
Read individual rule files for detailed explanations and code examples:
rules/form-useform-basic.md
rules/page-props-typing.md
rules/layout-persistent.md阅读单个规则文件获取详细说明和代码示例:
rules/form-useform-basic.md
rules/page-props-typing.md
rules/layout-persistent.mdProject Structure
项目结构
laravel-inertia-react/
├── SKILL.md # This file - overview and examples
├── README.md # Quick reference guide
├── AGENTS.md # Integration guide for AI agents
├── metadata.json # Skill metadata and references
└── rules/
├── _sections.md # Rule categories and priorities
├── _template.md # Template for new rules
├── page-*.md # Page component patterns (6 rules)
├── form-*.md # Form handling patterns (8 rules)
├── nav-*.md # Navigation patterns (5 rules)
├── shared-*.md # Shared data patterns (4 rules)
└── layout-*.md # Layout patterns (1 rule)laravel-inertia-react/
├── SKILL.md # 本文档 - 概览与示例
├── README.md # 快速参考指南
├── AGENTS.md # AI Agent集成指南
├── metadata.json # 技能元数据与参考链接
└── rules/
├── _sections.md # 规则类别与优先级
├── _template.md # 新规则模板
├── page-*.md # 页面组件模式(6条规则)
├── form-*.md # 表单处理模式(8条规则)
├── nav-*.md # 导航模式(5条规则)
├── shared-*.md # 共享数据模式(4条规则)
└── layout-*.md # 布局模式(1条规则)References
参考链接
- Inertia.js Documentation - Official Inertia.js docs
- Laravel Documentation - Laravel framework docs
- React Documentation - Official React docs
- Ziggy - Laravel route helper for JavaScript
- Inertia.js Documentation - Inertia.js官方文档
- Laravel Documentation - Laravel框架官方文档
- React Documentation - React官方文档
- Ziggy - Laravel路由的JavaScript辅助工具
License
许可证
MIT License
Copyright (c) 2026 Asyraf Hussin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License
Copyright (c) 2026 Asyraf Hussin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Metadata
元数据
- Version: 1.0.0
- Last Updated: 2026-01-17
- Maintainer: Asyraf Hussin
- Rule Count: 24 rules across 6 categories
- Tech Stack: Laravel 10+, Inertia.js 1.0+, React 18+, TypeScript 5+
- 版本: 1.0.0
- 最后更新: 2026-01-17
- 维护者: Asyraf Hussin
- 规则数量: 6个类别共24条规则
- 技术栈: Laravel 10+, Inertia.js 1.0+, React 18+, TypeScript 5+