React Router アプリケーションを Remix に移行する
世界中で展開されている何百万もの React アプリケーションが React Router によって支えられています。おそらく、あなたもいくつかリリースしていることでしょう!Remix は React Router を基盤として構築されているため、大規模なリファクタリングを避けるために段階的に作業できる簡単な移行プロセスになるよう努めてきました。
まだ React Router を使用していない場合でも、再考する価値のあるいくつかの説得力のある理由があると考えています!履歴管理、動的なパスマッチング、ネストされたルーティングなど、多くの機能があります。React Router のドキュメント を見て、私たちが提供するすべての機能を確認してください。
classic-remix-compiler: (Replace with actual link) remix-vite: (Replace with actual link) react-router: (Replace with actual link) react-router-docs: (Replace with actual link)
React Router v6 の使用を保証する
古いバージョンの React Router を使用している場合、最初のステップは v6 にアップグレードすることです。アプリを迅速かつ段階的に v6 にアップグレードするには、v5 から v6 への移行ガイド と 後方互換性パッケージ を参照してください。
Remixのインストール
最初に、Remixで開発するためにいくつかのパッケージをインストールする必要があります。以下の手順に従って、プロジェクトのルートディレクトリからすべてのコマンドを実行してください。
サーバーとブラウザのエントリポイントの作成
ほとんどのReact Routerアプリは主にブラウザで動作します。サーバーの唯一の仕事は、単一の静的HTMLページを送信することであり、React Routerはクライアント側でルートベースのビューを管理します。これらのアプリは一般的に、次のようなルートindex.js
のようなブラウザのエントリポイントファイルを持っています。
サーバーレンダリングされたReactアプリは少し異なります。ブラウザスクリプトはアプリをレンダリングするのではなく、サーバーが提供したDOMを「ハイドレート」します。ハイドレーションとは、DOM内の要素を対応するReactコンポーネントにマッピングし、イベントリスナーを設定してアプリをインタラクティブにするプロセスです。
2つの新しいファイルを作成しましょう。
app/entry.server.tsx
(またはentry.server.jsx
)app/entry.client.tsx
(またはentry.client.jsx
)
app
ディレクトリに配置されます。既存のアプリが同じ名前のディレクトリを使用している場合は、Remixに移行する際に区別するために、src
やold-app
のような名前に変更してください。
クライアントのエントリポイントは次のようになります。
root
ルートの作成
RemixはReact Routerの上に構築されていることを述べました。あなたのアプリは、JSXのRoute
コンポーネントで定義されたルートを持つBrowserRouter
をレンダリングする可能性があります。Remixではそれを行う必要はありませんが、それについては後で詳しく説明します。今のところ、Remixアプリが動作するために必要な最低レベルのルートを提供する必要があります。
ルートルート(Wes Bos風に言えば「ルートルート」)は、アプリケーションの構造を提供する役割を担います。そのデフォルトエクスポートは、他のすべてのルートがロードして依存する完全なHTMLツリーをレンダリングするコンポーネントです。アプリの足場またはシェルと考えてください。
クライアント側でレンダリングされるアプリでは、ReactアプリをマウントするためのDOMノードを含むindex HTMLファイルがあります。ルートルートは、このファイルの構造を反映したマークアップをレンダリングします。
app
ディレクトリにroot.tsx
(またはroot.jsx
)という新しいファイルを作成します。そのファイルの内容は異なりますが、index.html
が次のようになっていると仮定しましょう。
root.tsx
で、その構造を反映するコンポーネントをエクスポートします。
いくつかの点に注意してください。
noscript
タグを削除しました。サーバーサイドレンダリングを行うため、JavaScriptを無効にしたユーザーでもアプリを表示できます(そして、時間とともに漸進的エンハンスメントを改善するためのいくつかの調整を行うと、アプリの大部分が機能するようになります)。- ルート要素内では、
@remix-run/react
からOutlet
コンポーネントをレンダリングします。これは、通常React Routerアプリで一致したルートをレンダリングするために使用するコンポーネントと同じです。ここでは同じ機能を果たしますが、Remixのルーターに合わせて調整されています。
public
ディレクトリからindex.html
を削除してください。ファイルを保持すると、/
ルートにアクセスしたときに、RemixアプリではなくそのHTMLがサーバーから送信される可能性があります。
既存のアプリコードの適応
まず、既存のReactコードのルートをapp
ディレクトリに移動します。プロジェクトルートのsrc
ディレクトリにルートアプリコードが存在する場合は、app/src
に移動する必要があります。
このディレクトリの名前を変更して、これが古いコードであることを明確にすることをお勧めします。これにより、最終的にすべてのコンテンツを移行した後、削除することができます。このアプローチの利点は、アプリが通常どおりに動作するために、一度にすべてを行う必要がないことです。デモプロジェクトでは、このディレクトリをold-app
と名付けています。
最後に、ルートApp
コンポーネント(root
要素にマウントされていたコンポーネント)で、React Routerの<BrowserRouter>
を削除します。Remixは、プロバイダーを直接レンダリングする必要なく、これを処理します。
インデックスルートとキャッチオールルートの作成
Remixはルートルート以外に<Outlet />
で何をレンダリングするかを知るためのルートを必要とします。幸いなことに、既にアプリ内で<Route>
コンポーネントをレンダリングしており、Remixはルーティング規則を使用するように移行する際にそれらを使用できます。
まず、app
内にroutes
という新しいディレクトリを作成します。そのディレクトリ内に_index.tsx
と$.tsx
という2つのファイルを作成します。$.tsx
はキャッチオールルートまたは「スプラット」ルートと呼ばれ、まだroutes
ディレクトリに移動していないルートを古いアプリで処理するために役立ちます。
_index.tsx
と$.tsx
ファイル内では、古いルートApp
からコードをエクスポートするだけです。
バンドラーをRemixに置き換える
Remixは、アプリの開発とビルドのために独自のバンドラーとCLIツールを提供します。おそらく、あなたのアプリはCreate React Appのようなものを使ってブートストラップされたか、あるいはWebpackを使ったカスタムビルド設定を持っているでしょう。
package.json
ファイルで、現在のビルドと開発スクリプトの代わりにremix
コマンドを使用するようにスクリプトを更新します。
そして、あっという間に!あなたのアプリはサーバーサイドレンダリングされ、ビルド時間は90秒から0.5秒に短縮されました⚡
ルートの作成
時間が経つにつれて、React Routerの<Route>
コンポーネントによってレンダリングされるルートを、独自のルートファイルに移行したいと思うでしょう。ルーティング規則で概説されているファイル名とディレクトリ構造がこの移行をガイドします。
ルートファイルのデフォルトエクスポートは、<Outlet />
でレンダリングされるコンポーネントです。そのため、App
に次のようなルートがある場合:
ルートファイルは次のようになります。
このファイルを作成したら、App
から<Route>
コンポーネントを削除できます。すべてのルートの移行が完了したら、<Routes>
、そして最終的にold-app
内のすべてのコードを削除できます。
落とし穴と次のステップ
この時点で、最初の移行が完了したと言えるかもしれません。おめでとうございます!しかし、Remixは一般的なReactアプリとは少し異なる方法で動作します。そうでなければ、そもそもなぜそれを構築する手間をかけたのでしょうか?😅
安全でないブラウザ参照
クライアントレンダリングコードベースをサーバーレンダリングコードベースに移行する際のよくある問題点は、サーバー上で実行されるコードにブラウザAPIへの参照が含まれている可能性があることです。状態の初期化時に見られる一般的な例を以下に示します。
この例では、localStorage
がグローバルストアとして使用され、ページのリロードを跨いでデータを保持しています。useEffect
内でcount
の現在の値でlocalStorage
を更新していますが、useEffect
はブラウザでのみ呼び出されるため、これは完全に安全です!しかし、localStorage
に基づいて状態を初期化することは問題です。このコールバックはサーバーとブラウザの両方で実行されるためです。
解決策として、window
オブジェクトをチェックし、ブラウザでのみコールバックを実行することを考えるかもしれません。しかし、これは別の問題、恐ろしいハイドレーションの不一致につながる可能性があります。Reactは、サーバーでレンダリングされたマークアップがクライアントのハイドレーション中にレンダリングされるものと同一であることに依存しています。これにより、react-dom
はDOM要素とその対応するReactコンポーネントを一致させる方法を知り、イベントリスナーをアタッチし、状態の変化に合わせて更新を実行できます。そのため、ローカルストレージがサーバーで初期化したものとは異なる値を返す場合、新たな問題に対処する必要があります。
クライアントのみのコンポーネント
ここで考えられる解決策の1つは、サーバーで使用でき、ルートのローダーデータからプロップとしてコンポーネントに渡される、異なるキャッシングメカニズムを使用することです。しかし、サーバー上でコンポーネントをレンダリングすることがアプリケーションにとって重要ではない場合は、サーバーでのレンダリングを完全にスキップし、ハイドレーションが完了するまでブラウザでのレンダリングを待つという、より簡単な解決策があります。
この解決策を簡素化するために、remix-utils
コミュニティパッケージのClientOnly
コンポーネントの使用をお勧めします。その使用方法の例は、examples
リポジトリにあります。
React.lazy
と React.Suspense
React.lazy
と React.Suspense
を使用してコンポーネントを遅延読み込みしている場合、使用しているReactのバージョンによっては問題が発生する可能性があります。React 18までは、React.Suspense
は当初ブラウザのみの機能として実装されていたため、サーバーでは動作しませんでした。
React 17を使用している場合、いくつかの選択肢があります。
- React 18にアップグレードする
- 上記で説明されているクライアント側のみのアプローチを使用する
- Loadable Componentsなどの代替の遅延読み込みソリューションを使用する
React.lazy
とReact.Suspense
を完全に削除する
Remixは管理するすべてのルートのコード分割を自動的に処理するため、routes
ディレクトリに要素を移動する際には、React.lazy
を手動で使用することはほとんど(もしあれば)必要ありません。
react-lazy: (link to React.lazy documentation) react-suspense: (link to React.Suspense documentation) client-only-approach: (link to client-only approach documentation) loadable-components: (link to Loadable Components documentation)
設定
さらなる設定は任意ですが、開発ワークフローの最適化に役立つ可能性のあるものが以下に示されています。
remix.config.js
すべてのRemixアプリは、プロジェクトルートにremix.config.js
ファイルを受け入れます。設定はオプションですが、明確にするためにいくつか含めることをお勧めします。利用可能なすべてのオプションの詳細については、設定に関するドキュメントを参照してください。
docs-on-configuration: (This needs to be replaced with the actual link to the documentation)
jsconfig.json
または tsconfig.json
TypeScriptを使用している場合、プロジェクトにtsconfig.json
が既に存在する可能性があります。jsconfig.json
はオプションですが、多くのエディターにとって役立つコンテキストを提供します。 以下は、言語設定に含めることをお勧めする最小限の設定です。
パスエイリアスを使用します。/_remix.config.js
のappDirectory
を変更する場合は、/_
のパスエイリアスも更新する必要があります。
TypeScriptを使用している場合は、適切なグローバル型参照を使用して、プロジェクトのルートにremix.env.d.ts
ファイルを作成する必要もあります。
非標準インポートに関する注意
現時点では、変更を加えずにアプリを実行できる可能性があります。Create React Appや高度に設定されたバンドラーを使用している場合、スタイルシートや画像などのJavaScript以外のモジュールを含めるためにimport
を使用している可能性があります。
Remixはほとんどの非標準インポートをサポートしておらず、それは正当な理由によるものだと考えています。以下は、Remixで遭遇する可能性のあるいくつかの違いとその移行時のリファクタリング方法の非網羅的なリストです。
アセットのインポート
多くのバンドラーは、画像やフォントなどの様々なアセットのインポートを可能にするプラグインを使用しています。これらは通常、アセットのファイルパスを表す文字列としてコンポーネントに取り込まれます。
Remixでは、基本的に同じように動作します。<link>
要素によって読み込まれるフォントなどのアセットについては、一般的にルートモジュールでインポートし、links
関数が返すオブジェクトにファイル名を含めます。ルートlinks
に関するドキュメントを参照してください。
SVG のインポート
Create React App やその他のビルドツールでは、SVG ファイルを React コンポーネントとしてインポートできます。これは SVG ファイルの一般的なユースケースですが、Remix ではデフォルトでサポートされていません。
SVG ファイルを React コンポーネントとして使用したい場合は、最初にコンポーネントを作成し、直接インポートする必要があります。React SVGR は、コマンドライン から、またはオンラインプレイグラウンドで(コピー&ペーストする場合)これらのコンポーネントを生成するのに役立つ優れたツールセットです。
react-svgr: (React SVGRへのリンクをここに挿入) command-line: (コマンドラインへのリンクをここに挿入) online-playground: (オンラインプレイグラウンドへのリンクをここに挿入)
CSS のインポート
Create React App や多くのビルドツールは、様々な方法でコンポーネントに CSS をインポートすることをサポートしています。Remix は、下記で説明するいくつかの一般的な CSS バンドルソリューションに加えて、通常の CSS ファイルのインポートもサポートしています。
links
エクスポート
Route Remixでは、通常のスタイルシートをルートコンポーネントファイルから読み込むことができます。スタイルシートをインポートしても、スタイルに対して特別な処理が行われるわけではなく、スタイルシートを読み込むために使用できるURLが返されます。コンポーネント内でスタイルシートを直接レンダリングするか、links
エクスポートを使用できます。
アプリのスタイルシートとその他のいくつかのアセットをルートルートのlinks
関数に移動してみましょう。
32行目では、個々の<link />
コンポーネントをすべて置き換えた<Links />
コンポーネントをレンダリングしていることに気付くでしょう。ルートルートでのみリンクを使用する場合、これは重要ではありませんが、すべての子ルートは、ここでレンダリングされる独自のリンクをエクスポートする場合があります。links
関数は、ユーザーが移動する可能性のあるページのリソースをプリフェッチできるPageLinkDescriptor
オブジェクトを返すこともできます。
現在、既存のルートコンポーネントで、直接またはreact-helmet
のような抽象化を介して、<link />
タグをクライアントサイドでページに挿入している場合は、それを行うのをやめ、代わりにlinks
エクスポートを使用できます。多くのコードと、場合によっては1つまたは2つの依存関係を削除できます!
CSS バンドリング
Remix は、CSS Modules、Vanilla Extract、およびCSS サイドエフェクトインポート を組み込みでサポートしています。これらの機能を使用するには、アプリケーションで CSS バンドリングを設定する必要があります。
まず、生成された CSS バンドルにアクセスするには、@remix-run/css-bundle
パッケージをインストールします。
次に、cssBundleHref
をインポートし、リンク記述子に追加します。これは、アプリケーション全体に適用されるように、ほとんどの場合 root.tsx
にあるでしょう。
CSS バンドリングの詳細については、ドキュメントを参照してください。
注記: Remix は現在、Sass/Less の処理を直接サポートしていませんが、それらを別個のプロセスとして実行して CSS ファイルを生成し、それを Remix アプリにインポートすることは可能です。
css-modules: CSS Modulesのリンク vanilla-extract: Vanilla Extractのリンク css-side-effect-imports: CSSサイドエフェクトインポートのリンク css-bundling: CSSバンドリングのドキュメントへのリンク
<head>
内でのコンポーネントのレンダリング
<link>
がルートコンポーネント内でレンダリングされ、最終的にルート <Links />
コンポーネントでレンダリングされるのと同様に、アプリはドキュメントの <head>
に追加のコンポーネントをレンダリングするために、いくつかのインジェクションのトリックを使用する場合があります。これは多くの場合、ドキュメントの <title>
や <meta>
タグを変更するために実行されます。
links
と同様に、各ルートは meta
関数をエクスポートすることもできます。この関数は、そのルートの <meta>
タグ(<title>
、<link rel="canonical">
、<script type="application/ld+json">
など、メタデータに関連する他のいくつかのタグも含む)をレンダリングするために必要な値を返します。
meta
の動作は links
とわずかに異なります。ルート階層内の他の meta
関数の値をマージする代わりに、各リーフルートは独自のタグのレンダリングを担当します。これは以下の理由からです。
- 最適なSEOのために、メタデータに対するより細かい制御が必要になることが多い
- Open Graph プロトコル に従う一部のタグの場合、タグの順序によって、クローラーやソーシャルメディアサイトによる解釈に影響を与え、Remix が複雑なメタデータの結合方法を想定するのは予測が難しくなる
- 一部のタグは複数の値を許可しますが、一部のタグは許可しません。Remix は、これらのすべてのケースをどのように処理したいかを想定すべきではありません
open-graph-protocol: [Open Graph protocolのURLをここに挿入]
インポートの更新
Remix は react-router-dom
から取得できるすべてを再エクスポートしますが、これらのモジュールを @remix-run/react
から取得するようにインポートを更新することをお勧めします。多くの場合、これらのコンポーネントには、Remix向けに最適化された追加の機能がラップされています。
変更前:
変更後:
最後に
包括的な移行ガイドを提供するために最善を尽くしましたが、Remixは多くの既存のReactアプリとは大きく異なるいくつかの重要な原則に基づいてゼロから構築されていることに注意することが重要です。現時点ではアプリが動作する可能性がありますが、ドキュメントを精査し、APIを探索するにつれて、コードの複雑さを大幅に削減し、エンドユーザーエクスペリエンスを向上させることができると思います。そこに到達するには少し時間がかかるかもしれませんが、「大きな仕事も少しずつやればできる」ということです。
さあ、あなたのアプリをRemixしましょう!その過程で生まれるもの気に入っていただけると思います!💿