mcpField
L
laravel-inertia-react

laravel-inertia-react

github.com/asyrafhussin開発952.0k 回インストール9 週間前に更新
リポジトリ

laravel-inertia-react について

Laravel、Inertia.js、React でフルスタックアプリを開発する人向けのリファレンススキルです。ページ、フォーム、ナビゲーション、共有データ、レイアウト、アップロードのルールと例をまとめています。

主な機能

  1. 01型付き Inertia ページコンポーネント
  2. 02useForm によるフォーム処理
  3. 03ナビゲーションと部分再読み込み
  4. 04共有データと認証
  5. 05永続レイアウトとネストレイアウト
  6. 06ファイルアップロードのパターン

活用シーン

  1. 01React で Inertia ページを構築
  2. 02Laravel のフォーム検証エラーを処理
  3. 03認証済みユーザーデータを共有
  4. 04進捗表示付きファイルアップロードの追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/asyrafhussin/agent-skills --skill laravel-inertia-react

インストール後、Claude Code で/laravel-inertia-reactを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

AGENTS.md
metadata.json
README.md
rules/
_sections.md
_template.md
form-dirty-tracking.md
form-file-uploads.md
form-progress-indicator.md
form-reset-handling.md
form-transform-data.md
form-useform-basic.md
form-useform-hook.md
form-validation-errors.md
layout-persistent.md
nav-external-links.md
nav-link-component.md
nav-preserve-state.md
nav-programmatic.md
nav-replace-history.md
page-component-structure.md
page-default-layout.md
page-head-management.md
page-partial-reloads.md
page-props-typing.md
page-scroll-preservation.md
shared-app-config.md
shared-auth-user.md
shared-flash-messages.md
shared-ziggy-routes.md

詳細

Laravel + Inertia.js + React

Laravel、Inertia.js、React を使用して最新のモノリシックアプリケーションを構築するための包括的なパターンです。シームレスなフルスタック開発のための 30 以上のルールが含まれています。

適用するタイミング

以下の場合に、このガイドラインを参照してください。

  • Inertia のページコンポーネントを作成する場合
  • useForm フックを使ってフォームを処理する場合
  • 共有データと認証を管理する場合
  • 永続レイアウトを実装する場合
  • ページ間を移動する場合

優先度別のルールカテゴリー

優先度カテゴリー影響度プレフィックス
1ページコンポーネントCRITICALpage-
2フォームとバリデーションCRITICALform-
3ナビゲーションとリンクHIGHnav-
4共有データHIGHshared-
5レイアウトMEDIUMlayout-
6ファイルアップロードMEDIUMupload-
7高度なパターンLOWadvanced-

クイックリファレンス

1. ページコンポーネント(CRITICAL)
  • page-props-typing - Laravel からページの props の型を定義する
  • page-component-structure - 標準的なページコンポーネントのパターン
  • page-head-management - Head を使ったタイトルとメタタグの管理
  • page-default-layout - ページにレイアウトを割り当てる
2. フォームとバリデーション(CRITICAL)
  • form-useform-basic - useForm の基本的な使い方
  • form-validation-errors - Laravel のバリデーションエラーを表示する
  • form-processing-state - フォーム送信の状態を処理する
  • form-reset-preserve - フォームデータをリセットするか保持する
  • form-nested-data - ネストされたフォームデータを処理する
  • form-transform - 送信前にデータを変換する
3. ナビゲーションとリンク(HIGH)
  • nav-link-component - ナビゲーションに Link を使う
  • nav-preserve-state - スクロール位置と状態を保持する
  • nav-partial-reloads - 変更された部分のみを再読み込みする
  • nav-replace-history - 履歴を置き換えるか追加する
4. 共有データ(HIGH)
  • shared-auth-user - 認証済みユーザーにアクセスする
  • shared-flash-messages - フラッシュメッセージを処理する
  • shared-global-props - グローバル props にアクセスする
  • shared-typescript - 共有データの型を定義する
5. レイアウト(MEDIUM)
  • layout-persistent - 永続レイアウトのパターン
  • layout-nested - ネストされたレイアウト
  • layout-default - デフォルトレイアウトの割り当て
  • layout-conditional - 条件付きレイアウト
6. ファイルアップロード(MEDIUM)
  • upload-basic - ファイルアップロードの基本
  • upload-progress - アップロードの進行状況を追跡する
  • upload-multiple - 複数ファイルのアップロード
7. 高度なパターン(LOW)
  • advanced-polling - リアルタイムポーリング
  • advanced-prefetch - ページを事前取得する
  • advanced-modal-pages - モーダルをページとして扱う
  • advanced-infinite-scroll - 無限スクロール

重要なパターン

TypeScript を使ったページコンポーネント
// 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>
    </>
  )
}
useForm を使ったフォーム
// 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>
    </>
  )
}
永続レイアウト
// 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 コントローラー
<?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'),
        ]);
    }
}
共有データ(HandleInertiaRequests)
<?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'),
            ],
        ]);
    }
}
フラッシュメッセージコンポーネント
// 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>
  )
}

使い方

詳しい説明とコード例については、各ルールのファイルを参照してください。

rules/form-useform-basic.md
rules/page-props-typing.md
rules/layout-persistent.md

プロジェクト構成

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)

参考資料

ライセンス

MIT ライセンス。このスキルは、教育および開発目的で現状のまま提供されます。

メタデータ

  • バージョン: 1.0.1
  • 最終更新日: 2026-01-17
  • メンテナー: Asyraf Hussin
  • ルール数: 6 カテゴリーにわたる 24 ルール
  • 技術スタック: Laravel 10+、Inertia.js 1.0+、React 18+、TypeScript 5+

コメント