新規にインストールしたRuby on RailsアプリのフロントエンドはERBとHotwireですが、デフォルトの他にさまざまな選択肢があります。私はHotwireの使用を強くお勧めしますが、事情により他の技術を選択肢とする場合もあります。
下記では個人的な意見も含みますが、現在の選択肢を簡単に紹介しました。
(なお「モダン」フロントエンドの代表としてReactに言及していますが、他のものでも同様です)
- デフォルトで使用できます。セットアップが簡単です。
- コード量が少なく、非常に効率よく開発できます。
- JSON APIを用意する必要がなく、シリアライズすら不必要なので、viewテンプレートの中で生きたオブジェクトを参照できます。必要なデータに簡単にアクセスできますので、ドメインモデルが複雑であっても効率よく開発できます。
- 画面に描画されたものだけがブラウザに送信されますので、不意のデータ漏洩の心配はほとんどありません。
- ERBはMinitestのController testおよびRSpec Request specでレンダーされます。その際に不正なgetterやメソッドへのアクセスなどがあればエラーになります。完璧ではありませんが、かなり多くの型エラーが簡単なテストで防げます。
- Hotwire等で高度なUI/UXが実現できます。
- 必要であればReactなどのモダンフロントエンドを埋め込むこともできます。
ここでは2000年ごろに多かったタイプのRuby on Rails ERB – Reactの組み合わせに言及しています。
- セットアップは複雑です。
- ReactのビルドはWebpack, Rspack, esbuild, Viteなどのバンドラーを使用します。
- ビルドされたJavaScriptを通常はRails ERBが生成したHTMLから呼び出し、Reactはその中の
<div id="root"></div>要素などにマウントします。
- Reactで複数のURL(複数のページ)に対応するためにReact Routerなどのクライアントルータも用意します。
- ReactをマウントするRails ERBを一つだけ用意することもありますし、各URLごとに個別のERBテンプレートを用意することもあります。個別のERBテンプレートを用意する理由はレガシー移行のためだったり(従来のERBページとの共存)、認証・認可のためだったりします。
- react-rails gemやreact_on_rails gemなどを使って、上記のステップの一部を代替することがあります。
- その他、jsbundling-rails、Webpacker、Shakapacker、Vite Ruby Rails Integrationを使いこともあります。
- HotwireとReactの工数の差で紹介しているように、JSON APIを導入しているためにReact SPAはHotwireと比べて大幅に工数が増えます。
- ドメインモデルが複雑な場合は、JSON APIにネストしたデータを組み込む工数がかかります。
- 機密データをJSON APIに載せてしまっても、画面に表示されないために気づけません。データ漏洩のリスクが高まります。
- フロントエンドとバックエンド双方がJSON API契約を守っていることの担保が困難です。下記のことを考慮する必要がありますので、かなりの作業が必要です。
- 認証はERBページと同じように、HTTP-only cookieを使用します。ERBページにCSRFトークンが載ってきますので、CSRF対策も簡単です。
- Inertia.jsもこちらに分類されます。
- Ruby on Railsとの共存が不要になるため、セットアップは簡略化されます。
- Hotwireと比較して工数が増大すること、および複雑なドメインモデルへの対応、機密データの漏洩、JSON API契約遵守の困難さについては先に紹介したReact SPAと同じです。
- 認証を考える必要があります。
- 以前はフロントエンドJavaScriptにトークン管理をさせて、APIリクエストヘッダーにトークンを載せる方法も多く使用されていました。
- 最近の潮流としてはHTTP-only Cookieを使った認証が多くなっています。ERBを使用した従来の方法と同じもので、認証処理が大幅に簡略化されます。この場合はCSRF対策をしないと脆弱性になります。
- Ruby on RailsをAPIモードで使用していて、後からHTTP-only Cookieに対応させた場合、注意が必要です。Cookie処理のミドルウェアを戻すだけでなく、忘れずにCSRF対策のミドルウェアも戻すようにしましょう。CSRFトークンの管理は別途考える必要があります。
なお、Next.jsのstatic exportを使ってReact SPAを作り方法もありますが、下記の点に注意する必要があります。
- Dynamic Routingが難しくなります。回避策として以下の方法を見かけます。
- Next.jsの前にNGINXなどのサーバを置き、
/bookings/[id]のようなURLを/bookings?id=[id]にrewriteする方法。他にもAWS CloudFront、Netlify redirectsやCloudflare Transform rulesなどがあります。
- 最初から
/bookings/[id]のようなURLを諦め、/bookings?id=[id]を使う方法があります。ただしこれをやると2000年ごろのウェブっぽくなりますので、なるべくなら避けたいです。
このようにReactとRailsを繋げること自体はとても一般的なのですが、その割には簡単ではありません。そこで注目されているのがInertia.jsです。
Inertia.jsを使うと5分もかからない非常に簡単なセットアップで、ReactとRailsを繋げることができます。
Inertia.jsは非常にopinionatedです。通常のReactのように色々なライブラリを技術選定して使うのではなく、フレームワーク作者たちが用意したルータを使い、作者たちが指定した方法でデータをReactコンポーネントに渡していきます(この方法がまたRailsと相性が抜群です)。その意味ではRailsの発想(The menu is omakase)に近く、Reactの良さを活かしつつも高速な開発が可能になります。
- セットアップが非常に簡単です。
- JSON APIを明確に用意する必要がなく、クライアントサイドからAPIにfetchするコードも不要なので、記述するコード量は少なくなります。効率よく開発ができます。
- JSON APIを明確に用意する必要はありませんが、データをサーバからブラウザに送ることに変わりはありません。シリアライズ可能なデータしか送信できず、生きたオブジェクトを使ってDOMを生成することはできません。ドメインモデルが複雑な場合は形を整える必要があります。
- 認証はERBページと同じようにHTTP-only cookieを使用するために簡単です。CSRF対策もInertia.jsがやってくれます。
- ページコンテンツは全てReactで描画されますので、ReactでできるUI/UXは基本的に可能です。
- ただし通常のSPA同様、複雑なドメインモデルへの対応、機密データの漏洩、JSON API契約遵守の困難さがあります。
Inertia.jsは独自の考え方があり、良さを活かすためには下記のことに留意する必要があります。
- データはページ専用に作ります: React SPAではしばしばモデル単位のAPIエンドポイントを用意して、1ページあたり複数回fetchをします。それに対してInertia.jsでは全てのデータを一気にブラウザに送信するページ専用 APIの考え方になります。
- Ruby on Railsとの共存が不要(そもそも難しい)ため、セットアップは簡略化されます。ただし異なるホストにデプロイすることになりますので、Cookieのドメイン設定やCORS対策をしておく必要があります。
- Hotwireと比較して工数が増大すること、および複雑なドメインモデルへの対応、機密データの漏洩、JSON API契約遵守の困難さについては先に紹介したReact SPAと同じです。
- 認証: ReactをSPAとして使用する場合、(CSRFにさえ気をつければ)HTTP-only cookieの認証で十分です。しかしNext.jsサーバを間に設置すると認証の選択肢が増えて、一気に迷います。実際に認証に苦労している例を多く見かけます。