ReactではしばしばSPAアーキテクチャが採用されます。しかしSPAは多くの問題を抱えています。むしろサーバでレンダリングされたHTML (例えばERB/Hotwire)にReact埋め込む方が優れているケースも多いです。
SPAのデメリット
MPA埋め込みのメリット
例えばApple StoreはMPAを中心に作成されており、一部ページでReactを埋め込んでいます。
下記はApple Storeを模写した例です。Reactの埋め込み方は完全に同じにはしていませんが、同じ効果があるような方法を使用しています。
模写の詳しい解説は別途こちらを確認してください。
<%= provide :head, javascript_include_tag("react_iphone", "data-turbo-track": "reload", type: "module") %> <div class="container container-lg mx-auto px-4 pt-16"> <div class="mx-auto min-w-[1028px] lg:max-w-5xl"> <div id="root"></div> </div> </div> <% if @catalog_data %> <script type="application/json" id="catalog-data"> <%= @catalog_data.tap { it[:images].transform_values! { image_path(it) } } .to_json .html_safe %> </script> <% end %>
javascript_include_tag "react_iphone"でReactアプリの本体のreact_iphone.jsxを読み込んでいます<div id="root"></div>に埋め込まれます。<script type="application/json" id="catalog-data">の箇所ではカタログのデータ(オプションごとの価格など)をJSON形式に変換し、記載しています
@catalog_dataをJSONとして出力するJSON APIを用意し、Reactコンポーネントからfetch()で読み込みます。しかしこの方法ではデータを画面に表示されるまでに無駄な遅延が発生し、UI/UXが劣化します。import React from "react"; import {createRoot} from "react-dom/client"; import {IPhoneShow} from "./react/components/IPhoneShow" document.addEventListener("turbo:load", () => { const dataJSON = document.getElementById('catalog-data').textContent const data = JSON.parse(dataJSON) const root = createRoot(document.getElementById("root")) root.render(<IPhoneShow catalogData={data}/>); });
turbo:loadイベントが発火し、以下の処理が行われます。なおturbo:loadはTurboが用意しているカスタムイベントで、Turboを使用しない場合はDOMContentLoadedイベントを使うのが一般的です <script type="application/json" id="catalog-data">にあったJSONのデータを読み込み、dataオブジェクトにセットしますIPhoneShowコンポーネントにdataをprops(catalogData)として渡し、これを<div id="root">の箇所に埋め込みます<script type="application/json" ...>...</script>にデータを埋め込めばERBからReactにデータを渡せます。ダイナミックなコンテンツであっても、無駄なリクエストを送信せずに、優れたUI/UXを保つことができます。