React Router アプリケーションを Remix に移行する
世界中の何百万人もの React アプリケーションが React Router によって支えられています。あなたはいくつかをすでに展開しているかもしれません! Remix は React Router をベースに構築されているため、移行を容易にするための作業を行ってきました。これは、大規模なリファクタリングを避けるために段階的に作業を進めることができます。
まだ React Router を使用していない場合は、再考する価値のある理由がいくつかあります! 履歴管理、動的なパスマッチング、ネストされたルーティングなど。React Router ドキュメント を見て、提供している機能を確認してください。
React Router v6 を使用するようにしてください
古いバージョンの React Router を使用している場合は、最初に v6 にアップグレードする必要があります。アプリを v6 に迅速かつ反復的にアップグレードするには、v5 から v6 への移行ガイド と下位互換性パッケージ をご覧ください。
migration-guide-from-v5-to-v6: [移行ガイドの URL をここに挿入] backwards-compatibility-package: [下位互換性パッケージの URL をここに挿入]
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 アプリとは少し異なる方法で動作します。もしそうじゃなければ、なぜわざわざ Remix を作ったのでしょうか? 😅
安全でないブラウザ参照
クライアントレンダリングされたコードベースをサーバーレンダリングされたコードベースに移行する際に頻繁に発生する問題の1つは、サーバーで実行されるコードにブラウザ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
を手動で使用する必要はほとんどありません。
設定
さらなる設定はオプションですが、以下の設定は開発ワークフローの最適化に役立ちます。
remix.config.js
すべての Remix アプリは、プロジェクトルートに remix.config.js
ファイルを受け入れます。設定はオプションですが、明確にするためにいくつか含めることをお勧めします。利用可能なすべてのオプションの詳細については、構成に関するドキュメント を参照してください。
jsconfig.json
または tsconfig.json
TypeScript を使用している場合は、プロジェクトに tsconfig.json
が既に存在する可能性があります。jsconfig.json
はオプションですが、多くのエディターに役立つコンテキストを提供します。これは、言語設定に含めることをお勧めする最小限の設定です。
~~/\_
パスエイリアスを使用して、ファイルがプロジェクト内のどこにあってもルートからモジュールを簡単にインポートします。remix.config.js
の appDirectory
を変更する場合は、~~/\_
のパスエイリアスも更新する必要があります。
TypeScript を使用している場合は、プロジェクトのルートに適切なグローバル型参照を含む remix.env.d.ts
ファイルを作成する必要もあります。
非標準インポートに関する注意
現時点では、アプリを何も変更せずに実行できるかもしれません。Create React App または高度に構成されたバンドラー設定を使用している場合、import
を使用してスタイルシートや画像などの非 JavaScript モジュールを含めている可能性があります。
Remix は、ほとんどの非標準インポートをサポートしていません。これは、良い理由があると考えています。以下は、Remix で遭遇する可能性のあるいくつかの違いと、移行時にリファクタリングする方法をまとめたものです。
非網羅的なリスト:
- CSS:
- Remix: CSS は、
styles.css
などのファイルにインポートします。import styles from "./styles.css";
- その他:
import "./styles.css"
- Remix: CSS は、
- 画像:
- Remix: 画像は、
import image from "./image.png";
のようにインポートします。 - その他:
<img src="./image.png" />
- Remix: 画像は、
- フォント:
- Remix: フォントは、
import "./fonts/font.woff2";
のようにインポートします。 - その他:
<link rel="stylesheet" href="./fonts/font.css" />
- Remix: フォントは、
上記以外にも、Remix では、import
を使用してインポートする他のモジュールやファイルの種類もサポートしていません。
移行時のリファクタリング方法:
- CSS: CSS ファイルを
styles.css
などのファイルにインポートします。import styles from "./styles.css";
- 画像: 画像を
import image from "./image.png";
のようにインポートします。 - フォント: フォントを
import "./fonts/font.woff2";
のようにインポートします。
これらの変更を行うことで、Remix でのアプリケーションの動作を安定させることができます。
アセットのインポート
多くのバンドラーは、画像やフォントなどの様々なアセットをインポートできるように、プラグインを使用しています。これらは通常、アセットのファイルパスを表す文字列としてコンポーネントに渡されます。
Remixでは、基本的に同じように動作します。 <link>
要素によってロードされるフォントなどのアセットの場合、通常はルートモジュールにインポートし、links
関数が返すオブジェクトにファイル名を指定します。 ルートのlinks
に関するドキュメントを参照してください。
SVG インポート
Create React App やその他のビルドツールでは、SVG ファイルを React コンポーネントとしてインポートできます。これは SVG ファイルの一般的なユースケースですが、Remix ではデフォルトでサポートされていません。
SVG ファイルを React コンポーネントとして使用したい場合は、まずコンポーネントを作成して直接インポートする必要があります。React SVGR は、これらのコンポーネントを コマンドライン から生成したり、コピー&ペーストする場合は オンラインプレイグラウンド で生成したりするのに役立つ優れたツールセットです。
CSS インポート
Create React App や他の多くのビルドツールでは、コンポーネントに CSS をインポートする様々な方法がサポートされています。Remix は、以下の説明するような一般的な CSS バンドルソリューションに加えて、通常の CSS ファイルのインポートもサポートしています。
links
エクスポートのルート
Remix では、通常のスタイルシートをルートコンポーネントファイルからロードできます。それらをインポートしても、スタイルに対して魔法のようなことは起こりません。代わりに、スタイルシートを必要に応じてロードするために使用できる URL が返されます。コンポーネントにスタイルシートを直接レンダリングするか、links
エクスポート を使用できます。
アプリのスタイルシートとその他のいくつかのアセットをルートルートの links
関数に移動してみましょう。
32 行目で、個別の <link />
コンポーネントをすべて置き換えた <Links />
コンポーネントをレンダリングしたことに注意してください。これは、ルートルートでのみリンクを使用する場合には関係ありませんが、すべての子ルートは独自のリンクをエクスポートする可能性があり、ここでもレンダリングされます。links
関数は、ユーザーが移動する可能性のあるページのリソースを事前に取得できるように、PageLinkDescriptor
オブジェクト を返すこともできます。
現在、既存のルートコンポーネントでページクライアント側で <link />
タグを挿入している場合、直接または react-helmet
などの抽象化を介して、それを行うのをやめて、代わりに 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アプリケーションにインポートすることは可能です。
<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 は、これらのすべてのケースをどのように処理すべきかを想定すべきではありません
インポートの更新
Remix は react-router-dom
から取得できるすべてを再エクスポートしており、@remix-run/react
からこれらのモジュールを取得するようにインポートを更新することをお勧めします。多くの場合、これらのコンポーネントは Remix に最適化された追加の機能と機能でラップされています。
変更前:
変更後:
最後に
この包括的な移行ガイドで可能な限り網羅したつもりですが、Remix は従来の React アプリの構築方法とは大きく異なるいくつかの重要な原則に基づいてゼロから構築されたことを覚えておくことが重要です。現時点ではアプリが動作する可能性がありますが、ドキュメントを精査し、API を探求するにつれて、コードの複雑さを大幅に削減し、アプリのエンドユーザーエクスペリエンスを向上させることができると思います。そこへたどり着くまでには少し時間がかかるかもしれませんが、一口ずつそのゾウを食べていけばいいのです。
さあ、あなたのアプリを Remix してみましょう! 💿 途中で作るもの気に入ると思いますよ!