Remixを使い始めた話

こんにちは、23卒の新卒エンジニアのfujinoです。今回は弊社のサービスでReactのフレームワークであるRemixを使い始めた話をしようと思います。

背景

弊社では今までVue.jsのフレームワークであるNuxt.jsを用いてフロントエンドを実装していました。 これは、採用当時は生のhtmlが使えるのが良いと思っていたことや、Vue.jsの経験のあるフロントエンジニアがチームにいたことが理由でした。

しかし、最近ではTypescriptとの親和性や、コミュニティの大きさなどの理由からReactの方が勢いがあるように感じます。 弊社でも少し前からReactに移行することを決定し、現在進行形でNuxt.jsからReactへの移行プロジェクトを進めています。

Reactの主要なフレームワークとして、Next.jsとRemixが挙げられます。 両者の違いとして、Next.jsはSSG(Static Site Generation)やSSR(Server Side Rendering)をサポートしているのに対して、Remixは基本的にSSRのみをサポートしています1 今回私が参加したプロジェクトではSSGが不要な案件だったため、Remixの方が使わない機能が少なくとっつきやすいと感じました。 そこで、このプロジェクトではRemixを採用することにしました。

このように社内のRemixへの移行実績が増えてきたので今回は改めて振り返ってみようと思います。

Remixの特徴

簡単な特徴

Remixの特徴は以下の通りです。

基本的にSSR

RemixはSSGをサポートしていないため、Next.jsよりもシンプルな実装が可能です。

Shopifyがサポート

Shopifyはカナダの大規模なEC会社です。 したがって、Remixは大規模なサービスの運用にも耐えうるフレームワークである、と考えて問題ないと思います。 実際、Future Flagsがドキュメントにあり、突然仕様が変更されて急にいろいろな箇所を実装しなければいけない、といったことはなさそうです。

「Web標準に準拠」という思想

ドキュメントのトップページに「Focused on web standards and modern web app UX, you’re simply going to build better websites」と記載されています。 RemixではWeb標準に準拠することを重視した設計がなされています。 例えば、データを取得するときはFetch API、データミューテーションを行うときはFormタグ、サーバーにデータを持たせるときはクッキーとセッションを用います。

Remixのドキュメントのトップページ

(https://remix.run/ より引用)

開発体験

Remixで何か新しいページを実装する際は以下の手順で行います。

1. Componentの実装

routeファイルにComponentを実装します。 これはReact同様に、stateに関する実装や、JSXまたはTSXでUIを実装します。

2. loaderとactionの実装

Remixでは、loaderとactionという関数が用意されています。 これらは、サーバー側で実行される関数です。 サーバーとクライアントの間でのデータをやり取りするのに使います。

使い方は、サーバーからクライアントへデータを渡す際はloaderやactionのreturnでデータを返し、Component内でuseLoaderDataやuseActionDataという関数も使ってデータを受け取ります。 クライアントからサーバーへデータを渡す際は、Formタグ、inputタグ、Buttonタグを使ってデータを送信し、actionでそのデータを受け取ります。

loaderとactionの違いは、loaderはデータを取得するための関数で、actionはデータを更新するための関数です。 loaderはレンダリングされるたびに実行される一方で、actionはFormタグでデータが送信されたときに実行されます。 また、loaderはそのルートに対してGETリクエストが送られたときに実行される一方で、actionはそのルートに対してGET以外のPOST, DELETE, PATCH, PUTリクエストが送られたときに実行されます。

Componentおよび、loaderとactionがあることで、サーバーとUIのデータが同期されます。 ユーザーがFormを用いてデータを送信すると、サーバー側でactionにデータが渡されデータの更新が行われます。 その後、loaderが実行され、最新のデータがクライアントに返されます。 最後に、クライアント側では渡されたデータを元にUIが更新されます。

Remixではこのデータの流れが自動で行われるため、開発者はデータの同期を意識せずに開発を進めることができ、コードの見通しもよくなります。

Remixでのデータの流れ

(https://remix.run/docs/en/main/discussion/data-flow より引用)

Routing Rules

Remix(v2以降)では、やや特殊なルーティングのルールがあります。 ここではRemixの基本的なルーティングのルールをいくつか紹介します。 詳しいルールは公式ドキュメントを参照してください。

ドット分割(Dot Delimited)

ファイル名の「.」(ドット)はURLでは「/」(スラッシュ)に置き換わります。

ドット分割(Dot Delimited)

ネスト(Nested Routes)

ドットで分割されたファイル名の前の部分が一致するファイル同士は親子関係が形成されます。 親子関係が形成されたファイル同士は、レイアウトの構成に使うことができます。 つまり、親routeのコンポーネントの中に子routeのコンポーネントを表示できます。

ネスト(Nested Routes)

こうすることで、UIの共通部分の実装をまとめることができます。 また、loaderやactionも親子で別々に実装できるので、データの取得や更新の実装もrouteごとに分けられ保守性が高まります。

それだけでなく以下の2つの利点もあります。

1つ目は、ページのロード時間が短縮します。 親子関係が形成されたrouteのloaderはそれぞれ並行に実行されるため、パフォーマンスが向上します。

2つ目は、Reactの機能であるError Boundaryの実装が容易になります。 Remixでは、クラッシュしたコンポーネントに最も近いError Boundaryがエラーをキャッチするため、親子関係があることでエラーのスコープを限定できます。 例えばサイドバーが親、メインコンテンツが子の関係になっていて、メインコンテンツでエラーが発生した場合、サイドバーのコンポーネントはクラッシュせずにエラーページを表示できます。

Remixで苦労した点

最後に、自分たちのサービスをRemixで実装するにあたり、苦労した点をいくつか紹介します。

「もっとみる」UIが実装しにくい

弊社のバックエンドでは、何かのログなど大量のデータが返りうるものはカーソルを用いたページネーションを用いています。 つまりフロントエンドの実装は、十分な量のデータが返る場合は初回のレンダリングでは決まった数だけを表示し、 それより多くのデータを表示する際は、再度APIを叩いて初回のレンダリングのデータと結合して表示するという実装をします。 これを、サーバーのデータとクライアントのデータが同期される設計のRemixで実装しようとすると単純には実装できないと感じました。 結果的に、通常のReactのようなuseStateを用いた実装になりました。 つまり、初回のレンダリングで取得したデータをstateに保持しておいて、「もっとみる」が押されたらfetcherでAPIを叩き、その結果をstateに結合する、という実装になりました。

loaderやactionの横断的な処理の実装がしづらい

Remixではloaderやactionの横断的な処理の実装がしづらいなと思う場面がありました。 Remixではentry.server.tsxというファイルが用意されており、そこで共通する機能の実装ができます。 しかし、例えばログインしている状態での共通処理など、ある条件での共通処理を書く時はここには書けず困ってしまいました。 routeのloaderやactionごとに同じ処理を書いたり、 Pathless Routes でネストを増やして、そのloaderで処理を書くことが考えられますが、いずれもわかりづらい実装と感じてしまいます。

routeのファイル名が長くなる

Remixのルーティングのルールで述べたように、ファイル名の「.」(ドット)はURLでは「/」(スラッシュ)に置き換わります。 また、上で述べたようなPathless Routesを用いてネストを増やすとさらに複雑なファイル構成になります。 複雑なパスになると自然とファイル名も長くなってしまいます。 そうすると、ファイル名がわかりづらくなり、少し開発体験が落ちるかなぁという印象を受けました。

まとめ

今回は、弊社のサービスでRemixを使い始めた話をしました。

RemixはReactのシンプルなフレームワークであり、Web標準に準拠していることが特徴です。 また、サーバーとUIのデータが同期されるようなアーキテクチャのフレームワークになっており、コードの見通しがよくなります。

弊社では、チーム体制上バックエンドエンジニアでもフロントエンドのコードを書く機会があります。 Remixはシンプルな構成となっているため、フロントエンドを専門にしていなくてもとっつきやすく、弊社に適していると感じました。 私自身も新卒で入社後ずっとバックエンドの実装を書いてきてRemixに入門しましたのは4か月前ですが、それなりにスムーズにキャッチアップできました。

今後も、社内サービスでまだNuxt.jsを使っているサービスがいくつかあるのでそれをRemixに書き換えていく予定です。


  1. 正確には.2.5.0からSPAモードもサポートしています。https://github.com/remix-run/remix/blob/main/CHANGELOG.md#v250 [return]