Railsフロントエンド。何を選ぶ?

新規にインストールしたRuby on RailsアプリのフロントエンドはERBとHotwireですが、デフォルトの他にさまざまな選択肢があります。私はHotwireの使用を強くお勧めしますが、事情により他の技術を選択肢とする場合もあります。

下記では個人的な意見も含みますが、現在の選択肢を簡単に紹介しました。
(なお「モダン」フロントエンドの代表としてReactに言及していますが、他のものでも同様です)

ERB, Hotwire

  • デフォルトで使用できます。セットアップが簡単です。
  • コード量が少なく、非常に効率よく開発できます。
  • JSON APIを用意する必要がなく、シリアライズすら不必要なので、viewテンプレートの中で生きたオブジェクトを参照できます。必要なデータに簡単にアクセスできますので、ドメインモデルが複雑であっても効率よく開発できます。
  • 画面に描画されたものだけがブラウザに送信されますので、不意のデータ漏洩の心配はほとんどありません。
  • ERBはMinitestのController testおよびRSpec Request specでレンダーされます。その際に不正なgetterやメソッドへのアクセスなどがあればエラーになります。完璧ではありませんが、かなり多くの型エラーが簡単なテストで防げます。
  • Hotwire等で高度なUI/UXが実現できます。
  • 必要であればReactなどのモダンフロントエンドを埋め込むこともできます。

ERBとReact SPAの共存

ここでは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 gemreact_on_rails gemなどを使って、上記のステップの一部を代替することがあります。
    • その他、jsbundling-railsWebpackerShakapackerVite Ruby Rails Integrationを使いこともあります。
  • HotwireとReactの工数の差で紹介しているように、JSON APIを導入しているためにReact SPAはHotwireと比べて大幅に工数が増えます。
  • ドメインモデルが複雑な場合は、JSON APIにネストしたデータを組み込む工数がかかります。
  • 機密データをJSON APIに載せてしまっても、画面に表示されないために気づけません。データ漏洩のリスクが高まります。
  • フロントエンドとバックエンド双方がJSON API契約を守っていることの担保が困難です。下記のことを考慮する必要がありますので、かなりの作業が必要です。
    • TypeScriptはJSON API契約遵守を保証してくれません。フロントエンド内部の型安全性しか担保してくれません。
    • Open API(Swagger)を書いただけではフロントエンドとバックエンドが遵守している保証にはなりません。
    • OpenAPI Generator等のcode generatorを使ってもフロントエンドのコードがOpen APIを遵守していることを確認するだけです。
    • Committee Rails gem等を使用してバックエンドのレスポンスがOpen APIを遵守していることは確認できます。
  • 認証はERBページと同じように、HTTP-only cookieを使用します。ERBページにCSRFトークンが載ってきますので、CSRF対策も簡単です。
  • Inertia.jsもこちらに分類されます

フロントはReact SPAのみ

  • 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年ごろのウェブっぽくなりますので、なるべくなら避けたいです。

Inertia.jsで作るReact SPA

このように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の考え方になります。

Next.jsサーバ

  • Ruby on Railsとの共存が不要(そもそも難しい)ため、セットアップは簡略化されます。ただし異なるホストにデプロイすることになりますので、Cookieのドメイン設定やCORS対策をしておく必要があります。
  • Hotwireと比較して工数が増大すること、および複雑なドメインモデルへの対応、機密データの漏洩、JSON API契約遵守の困難さについては先に紹介したReact SPAと同じです。
  • 認証: ReactをSPAとして使用する場合、(CSRFにさえ気をつければ)HTTP-only cookieの認証で十分です。しかしNext.jsサーバを間に設置すると認証の選択肢が増えて、一気に迷います。実際に認証に苦労している例を多く見かけます