mcpField
I
inertia-coder

inertia-coder

github.com/majesticlabs-dev開発5048 回インストール7 週間前に更新
リポジトリ

inertia-coder について

inertia-coderは、RailsバックエンドとReact、Vue、Svelteを組み合わせたInertia.jsのシングルページアプリ開発を支援するスキルです。セットアップ、ページコンポーネント、フォーム、共有プロパティ、ファイルアップロードを扱い、サーバー側ルーティングを活用します。

主な機能

  1. 01RailsとInertia.jsのセットアップ
  2. 02React、Vue、Svelteのページパターン
  3. 03useFormによるフォーム処理
  4. 04共有プロパティとフラッシュメッセージ
  5. 05進捗表示付きファイルアップロード

活用シーン

  1. 01Inertia.jsとRailsのアプリ構築
  2. 02サーバー提供のプロパティによるページ作成
  3. 03フォームとファイルアップロードの処理

インストール

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

$npx skills add https://github.com/majesticlabs-dev/majestic-marketplace --skill inertia-coder

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

同梱ファイル

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

references/
react-patterns.md
svelte-patterns.md
vue-patterns.md

詳細

Inertia.js + Rails

Inertia.js と Rails バックエンドを使用して、モダンなシングルページアプリケーションを構築します。

このスキルを使う場面

  • Rails で Inertia.js をセットアップする
  • Inertia のページコンポーネントを作成する
  • useForm フックでフォームを処理する
  • 共有プロップとフラッシュメッセージを管理する
  • API の複雑さを避けてクライアント側ルーティングを行う
  • 進行状況を追跡しながらファイルをアップロードする

Inertia.js とは?

Inertia.js を使うと、従来のサーバーサイドルーティングとコントローラーを使用して SPA を構築できます。

アプローチメリットデメリット
従来の Rails ビューシンプルで、サーバー側でレンダリングされるインタラクティブ性に制限がある
Rails API + React SPA完全な SPA 体験ルーティングの重複、複雑な状態管理
Inertia.jsSPA + サーバールーティング両方の利点を兼ね備える

クイックセットアップ

# Gemfile
gem 'inertia_rails'
gem 'vite_rails'
bundle install
rails inertia:install  # Choose: React, Vue, or Svelte
# config/initializers/inertia_rails.rb
InertiaRails.configure do |config|
  config.version = ViteRuby.digest

  config.share do |controller|
    {
      auth: {
        user: controller.current_user&.as_json(only: [:id, :name, :email])
      },
      flash: controller.flash.to_hash
    }
  end
end

コントローラーパターン

class ArticlesController < ApplicationController
  def index
    articles = Article.published.order(created_at: :desc)

    render inertia: 'Articles/Index', props: {
      articles: articles.as_json(only: [:id, :title, :excerpt])
    }
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to article_path(@article), notice: 'Article created'
    else
      redirect_to new_article_path, inertia: { errors: @article.errors }
    end
  end
end

React ページコンポーネント

// app/frontend/pages/Articles/Index.jsx
import { Link } from '@inertiajs/react'

export default function Index({ articles }) {
  return (
    <div>
      <h1>Articles</h1>
      {articles.map(article => (
        <Link key={article.id} href={`/articles/${article.id}`}>
          <h2>{article.title}</h2>
        </Link>
      ))}
    </div>
  )
}

useForm でのフォーム処理

import { useForm } from '@inertiajs/react'

export default function New() {
  const { data, setData, post, processing, errors } = useForm({
    title: '',
    body: ''
  })

  function handleSubmit(e) {
    e.preventDefault()
    post('/articles')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={data.title}
        onChange={e => setData('title', e.target.value)}
      />
      {errors.title && <div className="error">{errors.title}</div>}

      <textarea
        value={data.body}
        onChange={e => setData('body', e.target.value)}
      />
      {errors.body && <div className="error">{errors.body}</div>}

      <button disabled={processing}>
        {processing ? 'Creating...' : 'Create'}
      </button>
    </form>
  )
}

共有レイアウト

// app/frontend/layouts/AppLayout.jsx
import { Link, usePage } from '@inertiajs/react'

export default function AppLayout({ children }) {
  const { auth, flash } = usePage().props

  return (
    <div>
      <nav>
        <Link href="/">Home</Link>
        {auth.user ? (
          <Link href="/logout" method="delete">Logout</Link>
        ) : (
          <Link href="/login">Login</Link>
        )}
      </nav>

      {flash.success && <div className="alert-success">{flash.success}</div>}

      <main>{children}</main>
    </div>
  )
}

// Assign layout to page
Index.layout = page => <AppLayout>{page}</AppLayout>

ファイルアップロード

import { useForm } from '@inertiajs/react'

const { data, setData, post, progress } = useForm({
  avatar: null
})

<input
  type="file"
  onChange={e => setData('avatar', e.target.files[0])}
/>

{progress && <progress value={progress.percentage} max="100" />}

<button onClick={() => post('/profile/avatar', { forceFormData: true })}>
  Upload
</button>

ベストプラクティス

推奨事項
  • <a> タグの代わりに <Link> を使用する
  • 共通データ(認証、フラッシュメッセージ)を config 経由で共有する
  • サーバー側で検証する。クライアント側は UX のみを担当する
  • processing を使って読み込み状態を表示する
  • 一貫したナビゲーションにはレイアウトを使用する
禁止事項
  • window.location を使用しない。SPA が機能しなくなります
  • REST API を作成しない。Inertia に API は不要です
  • クライアント側でデータを取得しない。サーバーがプロップを提供します
  • Inertia ルーターを迂回しない。動作に支障が出ます

詳細リファレンス

フレームワーク固有のパターンについては、以下を参照してください。

  • references/react-patterns.md - React フック、TypeScript、高度なパターン
  • references/vue-patterns.md - Vue 3 Composition API のパターン
  • references/svelte-patterns.md - Svelte のストアとリアクティビティのパターン

コメント