バックエンドにLaravel、フロントエンドにReactを使いたい場合、ルーティングライブラリにNext.jsを採用し、LaravelはAPIサーバーとして利用するケースが多く見られる。
が、個人的にはこの構成は学習コストや保守性の観点であまりおすすめしておらず、 Inertia.jsを使ったほうが良いのではと思っている。ルーティングがLaravelだけで管理されるだけでなく、APIを意識しないシームレスなデータ連携ができるし、Next.jsほど複雑な仕組みではないので学習コストも低い。OpenAPIやRPC系ツールが目指したようなバックエンド・フロントエンド間の型安全性も Laravel Data や TypeScript Transformer を使うことで解決できる。
ということで、今回はこのあたりの技術について紹介する。
Inertia.js
Bladeは「型安全じゃない」「UIライブラリのエコシステムが弱い」「小さなコンポーネントには向かない」などの理由で色々辛くなりがち。ビューへの変数の受け渡しは実行時にしか検証できないため、不要な変数の検知やリファクタリングが難しく、補完も効かせづらい。
フロントエンドのロジック実装に関しては Alpine.js を使うと保守性が上がるものの、Bladeでコンポーネント指向に書くには少しハードルを感じるし、Alpine.jsはコンポーネント化にあまり向いていない印象。Web Components を使ったとしてもSSRの仕組みが別途必要になったりエコシステムがあまり強くない。
Inertia.js を使うとテンプレートエンジンのように React や Vue を利用でき、「型安全であり」「UIライブラリのエコシステムが強く」「小さなコンポーネント」を作れる。SSRも対応しているのでSEO系も問題なし。画面遷移もSPAな感じでいける。ReactやVueでフロントエンドを実装できるのは開発生産性だけではなく、エンジニアの技術的キャリア形成としても良さそうだし採用面でも期待できる。
Inertia.jsは以下のブログ記事にまとめていたりするので詳細はそちらで。
https://tech.yappli.io/entry/inertiajs
TypeScript Transformer
Inertia.jsを使うと「型安全になる」と言ったが、バックエンドとフロントエンドの境界(propsの受け渡し)はInertia.jsだけでは型安全にならない。そこで、TypeScript Transformerというツールを使うとPHPクラスをTypeScriptの型定義に変換できるため、Inertia.jsのprops受け渡しも型安全にできる。
具体的には Inertia.render()
するときにPHPのクラスを引数に指定し、そのPHPクラスの型定義をフロントエンドのpropsで利用すれば良い。Viteのカスタムプラグインでファイル監視しつつ自動的に型定義を生成する、といったことも簡単。
コントローラ例
Route::get('demo', function () {
return Inertia::render('demo', new DemoData('hoge', true));
})->name('demo');
ページコンポーネント例
// App.Data.DemoData の型がPHPのコードから自動生成される
export default function Demo(props: App.Data.DemoData) {
// ...
}
Laravel Data
FormRequestは「テストがしづらい」「補完が効かない」「型安全じゃない」といった課題があり、ApiResourceも同様の問題を抱えている。
Laravel Dataを使うとマジックメソッドを使っていないので「補完が効く」し「型安全」になる。また、インスタンス化も簡単でバリデーションもでき「テストもしやすい」。マジックメソッドを使わないので前述のTypeScript Transformerとの相性も良い。
その他
Inertia.jsで利用できるフロントエンドライブラリは React
Vue
Svelte
になる。UIライブラリの充実性と生成AIとの相性を考えてReactを選択するとよいのではないかと思っている。
実装するWebサイトがリッチなフロントエンドを必要としない場合でも
- 単純なページ表示であればpropsを受け取ってレンダリングするだけ
- stateやeffect管理は不要で、Reactの学習コストが気にならない
- ちょっとした表示コンポーネントの共通化も気軽にできる
- それでいて型安全、補完が効くので開発体験が良く、負債になりづらい
といった理由で採用する価値があると思っている。
以前はAlpine.jsを推していた のだがコンポーネント性の弱さや、ビュー全体の型安全性も担保できないので、今はInertia.js推しになっている感じ。
とはいえ越えられない壁はある
RSCやReact Router v7、RPC系のライブラリはフロントエンドとバックエンドがTypeScriptということもあり、データの受け渡しなど非常にシームレスな開発ができる。
この点はInertia.jsもそうだが、バックエンドで他の言語を使っている以上はこの開発体験を越えることはできない。また、TypeScriptほどの型表現を持っているバックエンドの言語は限られるため、そのあたりのインピーダンスミスマッチみたいなのもありそう。
とはいえ、バックエンドのエコシステムや開発生産性といったところで別言語のメリットが強い部分もあるため、このあたりはトレードオフとも言えそう。