React Japan light logo
article hero
Ryan Florence
ライアン・フローレンス
ソース英語記事

Remix vs Next.js

私たちに最も寄せられる質問は次のようなものです:

RemixはNext.jsと一体どう違うの?

この記事では私たちはこの質問を答えるべきのようです!私たちはこの議題をストレートに、そして何の感情的な議論なしに言及していきたいと思います。もしあなたがRemixのファンになってくれていて、この記事を見て、今すぐにでもNextよりもRemixの方がすごいぜ 😎、と自慢するようなツイートをしたい気持ちが出てきたとしても、もしできれば、自慢するというような形ではツイートをなるべくしないようにお願いしたいです。私たちは Vercel で働いている方々と Vercel が立ち上がる前から友達です。そして、彼らのやっていることはとても素晴らしく、私たちは彼らを尊敬しています。

ただ、勘違いをしてほしくないのは、私たちはRemixはNext.jsよりも優れている特徴があると思っています。(そうでなければ、私たちはRemix自体を開発しなかったでしょう…)

ぜひこの記事全体を一度お読みになってください。目を引くようなグラフやアニメーションが出てきますが、それ以外の部分にもたくさんの情報や考え方が詰まっています。この記事を読まれた後に、あなたのNext(次の)プロジェクトではRemixを使ってみようかな、とお考えになられることを私は願っています。(ダジャレ要素ありですが、狙ったわけではありません 😂)

要約

  • Remixは静的コンテンツの提供においてNext.jsと同等またはそれ以上の速さです
  • Remixは動的コンテンツの提供においてNext.jsよりも速いです
  • Remixは遅いネット環境でも高速なユーザーエクスペリエンスを実現します
  • Remixはエラーや中断、競合状態を自動的に処理しますが、Next.jsは処理しません
  • Next.jsはクライアントサイドのJavaScriptで動的コンテンツの提供を推奨していますが、Remixは推奨していません
  • Next.jsではデータの変更にクライアントサイドのJavaScriptが必須ですが、Remixは必須ではありません
  • Next.jsのビルド時間はデータとともに線形に増加しますが、Remixのビルド時間はほぼ即座であり、データとは切り離されています
  • Next.jsではデータがスケールすると、アプリケーションのアーキテクチャを変更し、パフォーマンスを犠牲にする必要があります
  • 私たちはRemixの抽象化がより優れたアプリケーションコードにつながると考えています

背景

Remix と Next.jsを比べるのに、もっとも公平なやり方はVerselチームが自ら開発したNext.jsのデモアプリを使うことだと私たちは考えました。

なぜなら、彼ら自身がアプリを開発したので、彼らが開発の中でした判断は、Next.jsを使ってあなたにどのようにアプリを開発して欲しいのかということが反映されていると思ったからです。そして、Vercelチームが最も誇りに思う機能も現れているはずです。

私たちはNext.jsの例題のページからEコマースの例を選択しました。私たちが気に入った実際の開発で使う機能がたくさんありますし、彼らが最も力を注いだ例だと思います。

  • Eコマースにおいて最初のページロードは重要である
  • 検索ページ上での動的データ
  • ショッピングカート上でのデータ操作
  • フレームワークがどのように抽象化を助けるのかを実際に見せることができる複数のEコマースプロバイダーを統合することができる能力

私たちは2つのバージョンを開発しました:

  • Minimal Port(最低限の移行)- Next.jsのコードをRemix上で動くようにコピー・ペースト・調整したものです。Next.jsのデモと同様にVercelにデプロイされています。フレームワーク以外の全てが一緒なのでフレーうわーくの比較に非常に適しています。
  • Rewrite(リライト)- Next.jsとRemixは実はAPIで同じ部分はあまりありませんし、RemixはNext.jsと違う構造で開発をすることができます。Remixのデザインを実行するために、この例題を典型的なRemixのアプリケーションとして書き換えました。そして、クイックな画像最適化ルートも実装したので、100%Remixでの実装になります。

このアプリでは、Remixについて私たちがクールだと思っているすべての機能(ネストされたルートなど)を活用することはできません。この質問に答えた後は、Remixのことだけを話せるようになりますので、お楽しみに!

さらに、私たちはこの記事を公開する前にVercelに共有しました。彼らの例題は古いバージョンのNext.jsで実行されていたので、彼らは例題を最新に更新したので、私たちは最新の例と比較するために時間をかけて再構築しました。

本当に、私たちはVercelが好きです

VercelはRemixにとって優れたデプロイ先ですし、私たちにとって彼らは友人であり、パートナーでもあります。私はRemixで開発したアプリを聞いたことのあるほとんどすべてのホスティングサービスにデプロイしてきましたが、Vercelの開発者体験は私にとって最もお気に入りでした。"Develop, Preview, Ship"という言葉には実際の効果があります。たとえば、今朝は@gt_codesと私がプロダクションのバグを解決しようとしていて、各プレビューデプロイメントが利用可能で、それぞれのスクリーンショットがあるので、数秒で問題のあるコミットを見つけることができました。素晴らしいです。

今では私たちは面白い関係です。私たちはただの友人やテクノロジーパートナーではなく、フレームワークの競争相手でもあります!ですので、私たちの友人、パートナー、競争相手であるVercelに対して、Leeはこの記事の背後にある動機を素晴らしく表現しています:

その背景から是非この投稿を読んでみてください。次に進んでいきましょう!

自己紹介

何かを構築した人々がそのものをどのように説明するかで、多くのことを知ることができると思います。(私をTwitterでフォローすると、私がRemixを一生懸命繰り返し説明している様子がわかると思います!)

Next.jsは次のように説明されています:

プロダクションのためのReactフレームワーク。Next.jsは、静的およびサーバーのハイブリッドなレンダリング、TypeScriptサポート、スマートなbundling、ルートの事前取得など、プロダクションに必要なすべての機能を備えた、最高の開発者体験を提供します。設定は不要です。

Next.jsはVercelによって開発されています。VercelプラットフォームのGitHubリポジトリを見ると、次のように述べられています:

Vercelは、ヘッドレスコンテンツ、Eコマース、またはデータベースと統合するために構築された、静的サイトとフロントエンドフレームワークのためのプラットフォームです。

Remixは次のように説明されています:

Remixは、モダンで高速かつ堅牢なユーザーエクスペリエンスを構築するためのエッジネイティブなフルスタックJavaScriptフレームワークです。Web標準によってクライアントとサーバーを統一し、コードについて考える必要を減らし、プロダクトについてより考えることができます。

それぞれの説明を対比してみてください。

ホームページ、ビジュアルコンプリート

RemixはNext.jsと同じくらい速いですか?

これは通常、人々が最初に尋ねる質問です。Next.jsはしばしば「デフォルトでパフォーマンス」というフレーズを使用していますが、それを十分に備えています!各アプリがページをどれだけ速く ”視覚的完了” なレンダリングができるかを見てみましょう。

私たちはこれらのサイトをWebPageTestで実行しました。これは、この記事の比較GIFを生成する素晴らしいツールです。すべての比較で、各フレームワークを5回実行し、そのうちの最良の結果を取得しました。

各比較の上には、アニメーションを生成した結果へのリンクになっている見出しがあります。WebPageTest.comで「テストを再実行」をクリックすることで、自分自身ですべてを検証することができます。

最初のものは、バージニアからインターネットへのケーブルモデム接続で実行されました。

ホームページ - バージニア - ケーブル

Remixは0.7秒で読み込まれ、Nextは0.8秒で読み込まれます

詳細に入る前に、まずは3つのバージョンが非常に高速であり、どれがより速いかを比較する価値さえないことを認識しましょう。そして、Next.jsのクッキーバナーの小さなアニメーションは「視覚的に完了」として考慮され、Remixサイトにそれが存在していないため、Next.jsにとっても少し不公平です。スローモーションで見てみましょう:

ホームページ - バージニア - ケーブル - スローモーション

これで、Next.jsが実際に0.8秒で完了していることがわかります。

再度言いますが、全ての比較が速いです。また、3Gネットワーク接続で同じテストを実行しましたが、すべてが速く、見た目もほぼ同じという結果は同じでした。

✅ RemixはNext.jsと同じくらい速いです

アプリが高速な理由

Next.jsが高速な理由:ホームページでは、getStaticPropsを使用した静的サイト生成(SSG)を使用しています。ビルド時に、Next.jsはShopifyからデータを取得し、ページをHTMLファイルにレンダリングしてパブリックディレクトリに配置します。サイトがデプロイされると、シングルオリジンサーバーを介す代わりに静的ファイルはエッジ(VercelのCDN)で提供されるようになります。リクエストが届くと、CDNは単純にファイルを提供します。データの読み込みとレンダリングはすでに事前に行われているため、訪問者はダウンロードとレンダリングのコストを払う必要はありません。また、CDNはグローバルに配置され、ユーザーに近い場所に配置されています(これが「エッジ」です)。そのため、静的に生成されたドキュメントのリクエストはシングルオリジンサーバーまで移動する必要がありません。

Remixポートが高速な理由:RemixはSSGをサポートしていないため、HTTPのstale-while-revalidateキャッシュ指示(ここでいうSWRはVercelのswrクライアントフェッチパッケージとは異なる)を使用しました。エッジにある静的ドキュメント(Vercelの同じCDN上でも)と最終的な結果は同じです。違いは、ドキュメントがそこに到達する方法です。

ビルド/デプロイ時にすべてのデータを取得してページを静的なドキュメントとしてレンダリングする代わりに、キャッシュはトラフィックがあるときに使用可能となります。ドキュメントはキャッシュから提供され、次の訪問者のためにバックグラウンドで再検証されます。SSGと同様に、トラフィックを受けるときに訪問者はダウンロードとレンダリングのコストを支払う必要はありません。キャッシュミスについては、後ほど少し詳しく説明します。

SWRはSSGの素晴らしい代替手段です。Vercelへのデプロイが素晴らしいのも、彼らのCDNがSWRをサポートしていることです。

RemixのポートがNext.jsほど速くない理由について疑問に思うかもしれません。Remixにはまだ組み込みの画像最適化がないため、画像はNext.jsアプリに向けれています🤫。ブラウザは両方のドメインに接続を開く必要があり、これにより画像の読み込みが0.3秒遅れます(これはネットワークのウォーターフォールで確認できます)。画像が自己ホストされていれば、他の2つと同じくらいの0.7秒で表示されるでしょう。

Remixの高速化方法: SSGやSWRでドキュメントをエッジでキャッシュする代わりに、このバージョンではエッジ内のRedisでデータをエッジでキャッシュします。実際には、Fly.ioを使用してエッジでアプリケーションを実行しています。最後に、Resource Routeを使用してクイックな画像最適化を行い、永続ボリュームに書き込みされます。要は独自のCDNです 😎。

数年前にはこれを構築するのは難しかったかもしれませんが、サーバー業界は過去数年間で大きく変化し、ますます良くなっています。

動的ページの読み込み

RemixはNext.jsとはどう違うのですか?

これは私たちがよく聞かれる次の質問です。機能の種類には多くの違いがありますが、大きなアーキテクチャの違いは、Remixが速度のためにSSGに依存しないことです。

ほとんどのアプリでは、SSGではサポートできないケースに必ず遭遇します。ここで比較しているアプリケーションでは、検索ページにあたります。

制約として、ユーザーは無限にクエリを送信することができます。世界の現在の空間と時間の制約により、無限にページを静的に生成することはできません。なのでSSGは適用できません。

検索ページ - キャッシュ済み - バージニア - ケーブル

Remix in 0.8s, Next.js 1.9

SSGは動的なページには適応しないため、Next.jsはユーザーのクライアントサイドでブラウザからのデータ取得に切り替えました。ネットワークのウォーターフォールを見ると、なぜRemixよりも2.3倍遅いのかがわかります。

Remixの検索
remix-waterfall
Next.jsの検索
next-waterfall

Remixアプリは、Next.jsアプリが画像の読み込みを開始する前に完全に完了しています。ウェブパフォーマンスで重要なことは、ネットワークのウォーターフォールを並列化することです。Remixでは、このことについて非常に熱心です。

Next.jsが遅い理由: Next.jsは、私たちが「ネットワークウォーターフォールリクエストチェーン」と呼んでいるものを導入しました。SSGはここでは使用できないため、アプリは検索結果をユーザーのブラウザから取得しています。データを取得するまで画像を読み込むことができず、JavaScriptを読み込み、解析し、評価するまでデータを取得することができません。

クライアントでの取得は、ネットワークを介してより多くのJavaScriptとパース/評価にかかる時間を意味します。パース/評価については忘れがちですが、15番目のリクエストでのJSの実行時間がドキュメントのダウンロード時間よりも長くなっていることがわかります! Next.jsは、Remixよりも1.5倍多いJavaScriptを送信しており、アンパック時には566 kB vs. 371 kBです。ネットワーク上では、圧縮された状態で50 kB多くなっています(172 kB vs. 120 kB)。

ブラウザでの作業量が増えると、処理が蓄積されていきます。CPUの利用率とブラウザのメインスレッドのアクティビティを示す下部の行を見てください。Next.jsアプリは、大きな赤い「長いタスク」によって遅くなっています。

Remixがホームページと同じく高速な理由: 実際のRemixの例では、リクエスト時にShopify APIと通信する必要はありませんでした。SSGは検索ページをキャッシュすることはできませんが、RemixのバージョンではSWRまたはRedisを使用してキャッシュすることができます。ページを生成するための単一の動的な方法がある場合、アプリケーションコードを変更せずにキャッシュ戦略を調整することができます。その結果、よく訪れられるページでSSGの速度が実現されます。"/search"ページや左側のナビゲーションのカテゴリ、"tshirt"のようなよく使用されるクエリは、おそらく使用可能化されているでしょう。

動的ページのキャッシュミス

でも、キャッシュミスはどうなるの?

これについては、おそらく私の言葉だけでは信じてもらえないかもしれませんし、私自身もキャッシュが空だったことを証明する方法はありませんが、ここにRemixのキャッシュミスがあります(死んで、目に針を刺すほどに心の底から誓います)

検索ページ - キャッシュなし - バージニア - ケーブル

Remixは3.9秒で読み込まれ、Nextは8秒かかります

実は、私は嘘をつきました。これはRemix Rewriteのキャッシュヒットです。キャッシュミスの方が速かったです(0.6秒 🤭)。本当に信じてもらえないと思ったので、遅いキャッシュヒットをグラフィックに入れました 😅

ありえない!

実は、Shopify APIは非常に高速です。

Next.jsアプリはブラウザから直接Shopify APIに取得を行っているため、テストのネットワークグラフを見ると、リクエストにかかった時間はわずか224msであることがわかります。リクエストを行うよりもブラウザがAPIとの接続を確立するのに時間がかかりました!(初期のHTMLに<link rel="preconnect" />を追加することで、これを高速化することができます。)

ユーザーのブラウザがそんなに速くShopifyにリクエストを送信できるのであれば、Remixサーバーは確実により速く処理できます。ユーザーのクラウドへの接続は常にサーバーのクラウドへの接続よりも遅くなるため、データの取得はサーバー側に行うのが最適です。

結論として、Shopify APIを使用する場合、キャッシュはほとんど意味をなしません。キャッシュのヒットまたはミスは、ほとんど区別できません。

これは、ユーザーのネットワークを遅くして何が起こるかを確認することで最もよく示されます。では、今度は香港の3G接続から別のキャッシュミスを行ってみましょう。

検索ページ、キャッシュなし、香港、3G

Remixは3.1秒で読み込まれ、Nextは6.6秒で読み込まれます

Next.jsはキャッシュミスでも3.5秒遅れています。何が起きているのでしょうか?

Shopify APIは速いと言ったのに!

Next.jsはデータを読み込むまで画像を読み込むことができず、JavaScriptを読み込むまでデータを読み込むことができず、ドキュメントを読み込むまでJavaScriptを読み込むことができません。その連鎖のすべてのステップにおいてユーザーのネットワークは、じょうす的に増えていきます 😫。

Remixでは、連鎖はドキュメントが画像を読み込むことができるようになるまで待つだけです。サーバー上で常にフェッチするRemixの設計により、ユーザーのネットワークは他のすべての場所での乗数としては存在しません。

Remixでは、リクエストを受け取った時点でShopifyからのフェッチをすぐに開始することができます。ブラウザがドキュメントをダウンロードしてからJavaScriptをダウンロードするまでを待つ必要はありません。ユーザーのネットワークがどれだけ遅くても、サーバー上のShopify APIへのフェッチは変わらず、おそらく200ms未満です。

アーキテクチャの分岐

Next.jsがクライアントでフェッチするように移行したことにより、ユーザー体験だけでなく、Shopifyとの通信に関する2つの異なる抽象化が生まれました:SSGのための抽象化とブラウザのための抽象化です。

このようなアーキテクチャの分岐はいくつかの重要な問いが出てきます。

  • ブラウザでユーザー認証する必要があるか?
  • APIはCORSをサポートしているか?
  • APIのSDKはそもそもブラウザで動作するか?
  • どのようにビルドコードとブラウザコード間でコードを共有するか?
  • APIトークンをブラウザで公開しても大丈夫か?
  • 全てのユーザーに送信した我々のトークンにはどのような権限があるか?
  • この関数はprocess.envを使用できるか?
  • この関数はwindow.location.originを読み取ることができるか?
  • クライアントとサーバーの両方の場所で動作するネットワークリクエストをどのように作成するか?
  • これらのレスポンスをどこかにキャッシュすることはできるか?
  • 両方の場所で動作する同型のキャッシュオブジェクトを作成し、異なるデータフェッチ関数に渡すべきか?

サーバー上でShopify APIを抽象化する必要があ流だけのRemix.runにおいて、これらの質問に答えましょう。

  • ブラウザでユーザー認証する必要があるか?(いいえ)
  • APIはCORSをサポートしているか?(問題なし)
  • APIのSDKはそもそもブラウザで動作するか?(必要なし)
  • どのようにビルドコードとブラウザコード間でコードを共有するか?(必要なし)
  • APIトークンをブラウザで公開しても大丈夫か?(必要なし)
  • 全てのユーザーに送信した我々のトークンにはどのような権限があるか?(そもそも送信していません!)
  • この関数はprocess.envを使用できるか?(はい)
  • この関数はwindow.location.originを読み取ることができるか?(いいえ)
  • 両方の場所で動作するネットワークリクエストをどのように作成するか?(ブラウザでレクエストしていないので、どのようにでも作成できます)
  • これらのレスポンスをどこかにキャッシュすることはできるか?(もちろんです。HTTP、redis、lru-cache、持続的なボリューム、sqliteなど、どこでもキャッシュできます)
  • 両方の場所で動作する同型のキャッシュオブジェクトを作成し、異なるデータフェッチ関数に渡すべきか?(必要ありません!)

これらの質問に対する回答が簡単であればあるほど、抽象化がより優れ、より取り扱いやすいシンプルなコードになります。

もしNext.jsアプリがクライアントフェッチから離れてgetServerSidePropsを使用するようになれば、このギャップを埋めることができ、これらの問いに対するよりシンプルな回答が得られるでしょう。興味深いことに、Next.jsのドキュメントはしばしばサーバーフェッチから離れてSSGやクライアントフェッチを推奨しています。

データを事前レンダリングする必要がない場合は、クライアントサイドでデータを取得することを検討するべきです。

また、ユーザーデータを持つページではクライアントフェッチを推奨しており、再びアーキテクチャの分岐を促しています。

[クライアントフェッチ] はたとえば、ダッシュボードでうまく機能します。ダッシュボードはプライベートでユーザー固有のページであるため、SEOとは関係ありません。

ここで見たように、サーバーレンダリングはSEOだけでなく、パフォーマンスの向上にも関係しています。

基本的な違いは、Next.jsにはページ上でデータを取得するための4つの「モード」があることです。

  • getInitialProps - サーバーとクライアントの両方で呼び出されます
  • getServerSideProps - サーバーサイドで呼び出されます
  • getStaticProps - ビルド時に呼び出されます
  • クライアントフェッチ - ブラウザで呼び出されます

Remixにはloaderしかありません。一つの場所で実行されるものを抽象化する方が、三つの場所で実行される四つのものを抽象化するよりも簡単です。

アーキテクチャの分岐のコスト

このアーキテクチャの分岐のコストを定量化してみましょう。このアプリの中で最も難しい開発タスクは、Eコマースバックエンドの抽象化です。このアプリは、Shopify、BigCommerce、Spree、Saleorなど、何でも接続できるように設計されています。

Next.jsアプリでは、Shopifyの統合はこのフォルダにあります。現在のclocの実行結果は次のとおりです。

     101 text files.
      93 unique files.
       8 files ignored.
 
github.com/AlDanial/cloc v 1.92
---------------------------------------------------------------------
Language           files          blank        comment           code
---------------------------------------------------------------------
TypeScript            88            616           2256           5328
GraphQL                1           1610           5834           2258
Markdown               1             40              0             95
JSON                   2              0              0             39
JavaScript             1              1              0              7
---------------------------------------------------------------------
SUM:                  93           2267           8090           7727
---------------------------------------------------------------------

ほぼ100のファイルにまたがる約8,000行のコードです。試してみましたが、他の統合に対しても同じような結果が得られます。すべての統合は100のファイルに近づき、約10,000行のコードになります。ほとんどのコードはブラウザにも届きます。

ここにはRemixとShopifyの統合は以下のとおりです。

  • 1つのファイル
  • 608行のコード
  • どれもブラウザには送られません

これがアーキテクチャの分岐のコストです。Next.jsの抽象化は、ビルドとブラウザの両方の場合を予測し、実行可能にする必要があります。Remixの抽象化はサーバーのみです。

もしかしたら、2つのShopifyプロバイダーが同じ機能を持っているのか疑問に思うかもしれません。そして、私たちがそのように欺いている可能性もあります。多くのプロバイダーには認証やウィッシュリストのためのコードがありますが、Shopifyプロバイダーはどちらも使用していませんでした(ただし、それらのためのモジュールをエクスポートする必要がありました)。2つのウェブサイトを使用してみると、同じ機能を持っていることがわかります。いずれにせよ、私たちがもし何かを見落としていたとしても、アプリで表示されている機能に必要となったコードは1/10であることに対して、7,000行のコードが必要になるとは想像しにくいです。

たとえNext.jsが検索ページをgetServerSidePropsに移行したとしても、依然としてデータ変更機能にはそれらほぼすべてのコードが必要になるでしょう。ちょっと今は話を進めすぎてますね!

Edge Native

「エッジへのデプロイ」についてよく話しますが、それはどういう意味でしょうか?今回は、高速なユーザーネットワークを持つ香港での別のキャッシュミスの例を紹介します。

検索ページ - キャッシュなし - 香港 - ケーブル

Nextは8秒で読み込まれ、Remixは3.9秒かかります

今回は、2つのRemixアプリの違いについて話します。すでに話に出てきたように、Next.jsのバージョンはネットワークのウォーターフォールチェーンのために遅くなっています。

両方のRemixアプリはサーバーでフェッチをしていますが、なぜRemix Portの方がRemix Rewriteよりも遅いのでしょうか?

答えは簡単です。Remix PortはVercelの関数で実行されており、Vercelの関数はあなたのコードをエッジで実行しません。デフォルトではワシントンDC地域で実行されます。なので、香港からはかなり遠い場所です!

つまり、サーバーがShopifyからデータをフェッチし始める前に、ユーザーは香港からワシントンDCまで行かなければなりません。サーバーのフェッチが終わったら、ドキュメントを遠くまで送り返さなければなりません。

一方、Remix RewriteもワシントンDCで実行されていますが、同時に香港でも実行されています!つまり、ユーザーはすべての処理がより速くなりますRemixサーバーへの非常に迅速なアクセスがなされます。

それは、ある町に行くために自転車で駅まで行って、電車に乗ることと、全ての道のりを自転車で行くことの違いです。

🚲-----------------------------------------🏢
🚲-----🚊====🏢

通常通り、ネットワークのウォーターフォールでこれが表れています:

Remix Rewrite @ エッジ
rewrite-waterfall
Remix Port @ US East
port-waterfall

インフラストラクチャの違いは、ドキュメントの最初の青いバーで現れます。Remix Portでは、はるかに大きくなっています。それはユーザーがVercelの関数自転車道を使って世界の半分を自転車で走ることを意味します。Remix Rewriteでは、電車に乗ってShopify APIに行き、より早く戻ってきました。

このバージョンは、世界中の数十の地域でNode.jsサーバーを実行できるFly.io上で実行されます。ただし、RemixはNode.jsに依存していません。任意のJavaScript環境で実行できます。実際、すでにCloudflare Workersで実行されています。これは、世界中に分散された250のサーバーでコードが実行されていることを意味します。これ以上ユーザーに近い場所で実行されることはもはやないです!

これが、Remixが「エッジネイティブ」であると言っている理由です。Next.jsはNode.jsに依存しているため、現在はエッジへの展開能力が制限されています。

この領域では、開発者体験を向上させるためにまだ多くの作業が必要です。現時点では、公式にはNode.jsとCloudflareのみをサポートしていますが、Denoを積極的に取り組む活動をしており、コミュニティメンバーはFastly上でRemixを実行しています。

Remixのような「エッジネイティブ」フレームワークを使用すると、どのユーザーに対して高速な体験を提供するのかを選択する必要がそもそもなくなります。世界中のどこにいても、高速な体験をすべてのユーザーに提供できるのです。

Remixはエッジのために作られました。そして、ご覧になったとおり、非常に有望なものになっています。Vercelチームもアプリをエッジに展開するために一生懸命取り組んでいるとのことです。Remixはすでに対応可能になっており、今後より試していくのが待ちきれません。

クライアントサイドのトランジション

両方のフレームワークは、リンクのプリフェッチングによる瞬時のトランジションを可能にしますが、Next.jsはSSGから作成されたページに対してのみこの処理を行います。検索ページはまたもや除外されてしまいました。(次回に期待しましょう)

しかし、**Remixはデータの読み込みのためのアーキテクチャの分岐がなかったため、どのページでもプリフェッチすることができます。**予測できないユーザーが操作する検索ページのURLをプリフェッチすることは、予測可能な商品のURLをプリフェッチすることと変わりません。

実際、Remixのプリフェッチはリンクに限定されていません。いつでも、どんな理由でも、任意のページをプリフェッチすることができます!これを見てください、ユーザーが入力するにつれて検索ページをプリフェッチしています。

検索入力のプリフェッチ、高速な3G

スピナーやスケルトンを使わず、遅いネットワークでも即座にユーザーエクスペリエンスを提供します🏎

これは非常に簡単に行えました。

import { Form, PrefetchPageLinks } from "@remix-run/react";
 
function Search() {
  let [query, setQuery] = useState("");
  return (
    <Form>
      <input type="text" name="q" onChange={(e) => setQuery(e.target.value)} />
      {query && <PrefetchPageLinks page={`/search?q=${query}`} />}
    </Form>
  );
}

RemixはHTMLの<link rel="prefetch">を使用しています(Next.jsのようなメモリキャッシュは使っていません)。そのため、Remixではなく、ブラウザが実際にリクエストを行います。ビデオを見ると、ユーザーが現在のフェッチを中断するとリクエストがキャンセルされる様子がわかります。Remixは非同期処理の優れたハンドリングのおかげで、1文字のコードもシップする必要はありませんでした。 #useThePlatform ... あるいは、うーん、#reuseThePlatform 😎?!

データの変更

ここで、RemixとNext.jsはまったく異なってくる点になります。アプリのコードの半分はデータの変更に関連しています。ウェブフレームワークもそれを尊重する時が来ました。

Next.jsでの変更の仕組み: Next.jsはここでは何もしていません。<button onClick={itsAllUpToYou}>。通常、フォームのステートを管理して投稿する内容を把握し、投稿するためのAPIルートを追加し、ローディングとエラーの状態を追跡し、データを再検証し、変更をUI全体に伝播させ、最後にエラーや中断、競合状態を対処する必要があります((でも正直になりましょう、みんな誰もそこまで実際には扱っていないですよね)

Remixでの変更の仕組み: RemixはHTMLフォームを使用します。ここであなたはこう考えるでしょう。

ふんっ...ウェブアプリを作っているんだから、これは絶対に機能しないよ。

あなたはこれから紹介するAPIが、モダンなウェブアプリのニーズを処理する能力がないように見えるかもしれません。私の今までのキャリア全てで高度にインタラクティブなウェブアプリに関わってきましたので、Remixはそれを考慮して設計されました。これが昔のPHPのように見えるからといって、現代の洗練されたユーザーエクスペリエンスにスケールできないわけではありません。Remixはスケールアップすると言っていますが、スケールダウンもできます。だから、Remixを理解するために昔の日々に戻ってみましょう。

ウェブの黎明期から、ミューテーションはフォームとそれを処理するサーバーページとしてモデル化されています。Remixを完全に無視すると、次のようになります:

<form method="post" action="/add-to-cart">
  <input type="hidden" name="productId" value="123" />
  <button>Add to Cart</button>
</form>
// on the server at `/add-to-cart`
export async function action(request) {
  let formData = await request.formData();
  return addToCart(formData);
}

ブラウザは、フォームのシリアライズされたデータを使用して "/add-to-cart" に POST し、保留中の UI を追加し、完了したらデータベースからのすべての新しいデータを持つ新しいページをレンダリングします。

Remix は HTML フォームと同じことをしますが、大文字の <Form> とルートの action という関数(Next.js のページが独自の API ルートだと想像してください)を最適化しています。ドキュメントの再読み込みではなく fetch を使用して投稿し、その後、サーバーとの間でページ上のすべてのデータを再検証して、UI をバックエンドと同期させます。これは SPA で行っていることと同じですが、Remix がすべてを管理してくれるということです。

フォームとサーバーサイドのアクション以外に、サーバーとの変更を伝えるために必要なアプリケーションコードはありません。また、変更を他の UI に伝えるためのアプリケーションコンテキストプロバイダーやグローバルステート管理のトリックも必要ありません。これが、Remix のバンドルが Next.js のバンドルよりもほぼ30%小さい理由です。自分の "API ルート" と通信するためにそのすべてのコードは必要ありません。

おっと、また嘘をつきました。このコードは実際には Remix で動作します。小文字の <form> を使用すると、Remix ではなくブラウザがポストを処理します。JavaScript の読み込みに失敗する状況では便利です 😅(後で詳しく説明します)

Remix には、ビジーなスピナーや進行状況、楽観的な UI を作成するために投稿されるデータに関するトランジションについて尋ねることで、楽観的な UI にスケールアップすることもできます。このモデルは HTML フォームであり、はデザイナーのどのような要望にも対応可能です。そして、「何の問題もありまえん。実行可能です。」と言うために、実装を完全に再設計する必要はありません。

Remix がここで行ってくれることは、より小さいサイズのバンドルとシンプルなミューテーション API だけではありません。

Remix はサーバーとのすべてのやり取り(データの読み込みとデータの変更)を処理するため、ウェブアプリの長年の問題を解決するというウェブフレームワーク業界の中でユニークな能力を持っています。

未処理のエラー

「カートに追加」のバックエンドハンドラがエラーを返した場合、どうなるでしょうか?ここでは、カートにアイテムを追加するルートへのリクエストをブロックして、何が起こるかを確認します。

Next.jsのPOSTエラー

何も起こりません。エラーハンドリングは難しくて面倒です。多くの開発者はここでスキップしてしまいます。私たちはこれは非常に悪いデフォルトのユーザー体験だと考えています。

それでは、Remixではどうなるか見てみましょう。

RemixのPOSTエラー

Remixは、アプリ内のデータとレンダリングに関するすべてのエラーを処理します。サーバー上のエラーも含めてです。

あなたがやるべきことは、アプリのルートにエラーバウンダリを定義するだけです。さらに詳細に制御することもでき、エラーが発生したページの一部のみをダウンさせることもできます。

Remixがこれを実現できて、Next.jsがこれを実現できない、唯一の理由は、Remixのデータの抽象化がアプリにデータを取り込む方法だけでなく、それを変更する方法にまで及んでいるからです。

中断

ユーザーはよくボタンを間違って2回押してしまい、ほとんどのアプリはそのような場合をうまく対処しません。しかし、ユーザーが非常に速くボタンをクリックすることを予想しており、UIがすぐに反応することを想定しているボタンもあります。

このアプリでは、ユーザーはカート内のアイテムの数量を変更することができます。おそらく、数回、数量を増やすために、素早いクリックをするでしょう。

それでは、Next.jsアプリが中断にどのように対処するか見てみましょう。

Next.jsの中断処理

実際に何が起こっているかは少し見づらいですが、ビデオコントロールを前後して動かすとより良く見えます。中央部分で5から6から5へと変化する奇妙な動きがあります。最後の数秒が最も興味深いです。最後に送信されたリクエストが着地し(4になります)、数フレーム後に最初に送信されたリクエストが着地します!数量フィールドはユーザーの操作なしに5から4から2にジャンプします。信頼性の低いUIですね。

このコードは競合状態、中断、または再検証を管理していないため、UIはサーバーと同期していない可能性があります(2または4が最後にサーバーサイドのコードに到達したかどうかに依存します)。中断を管理し、変更後にデータを再検証することで、これを防ぐことができました。

競合状態や中断を扱うのは難しいと理解しています!だからほとんどのアプリはそれを行いません。Vercelチームは業界でも最も優れた開発チームの一つであり、彼らさえもスキップしました。

実際、私たちが前回のブログ記事でReactコアチームが作成したReact Server Componentsの例を移植したとき、彼らも同じバグを抱えていました。

先ほど述べたように、私たちはネットワークタブにこだわっています。Remixがこれをどのように処理するか見てみましょう。

Remixの中断処理

Remixでは中断時にリクエストをキャンセルし、POSTが完了した後にデータを再検証していることを見ることができます。これにより、フォームで行った変更だけでなく、ページ全体のUIがサーバーと同期していることが保証されます。

もしかしたら、Next.jsアプリよりも私たちのアプリは細部への注意があったと思うかもしれません。しかし、このすべて動作にはアプリケーションコードに含まれていません。RemixのデータミューテーションAPIに組み込まれています。((実際には、ブラウザがHTMLフォームで行うことと同じです...)

Remixのクライアントとサーバーのシームレスな統合と移行は前例のないものです。

Remix ❤️ ウェブ

私たちのウェブ開発の数十年のキャリアの中で、以前はどれだけシンプルだったかを覚えています。ボタンをフォームに配置し、データベースに書き込むページを指定し、リダイレクトして、更新されたUIを取得するだけでした。とても簡単でした。

RemixのAPIを設計する際には、常にプラットフォームを最初に考えます。たとえば、変更操作のワークフローです。HTMLフォームAPIとサーバーサイドのハンドラーが適切だとわかっていたので、それを基に構築しました。このことが目標ではなかったのですが、驚くほど素晴らしい副作用として、Remixアプリのコア機能はJavaScriptなしでも動作します!

JavaScriptを使ないRemix

Remixをこのように使用することは完全に有効ですが、JavaScriptを使用せずにウェブサイトを構築することは意図していません。私たちは素晴らしいユーザーインターフェースを構築するための大きな野心を持っており、そのためにはJavaScriptが必要です。

「RemixはJavaScriptなしで動作します」と言うよりも、「RemixはJavaScriptよりも先に動作します」と言いたいです。おそらく、あなたのユーザーはページがJavaScriptを読み込んでいる最中に列車のトンネルに入ったかもしれません。彼らがトンネルから出てきたとき、ページは通常通りに動作します。当初私たちは本当に単純なHTMLを目指していましたが、結果として非常に堅牢なフレームワークになりました。

サーバーサイドのコードを書くために、私たちはウェブプラットフォームを参考にしています。別の新しいJavaScriptのリクエスト/レスポンスAPIを作るのではなく、Remixでは**Web Fetch APIを使用しています。URLの検索パラメータを扱うために、Web APIのURLSearchParamsを使用します。フォームデータを扱うために、Web APIのFormDataを使用します。

export function loader({ request }) {
  // request is a standard web fetch request
  let url = new URL(request.url);
 
  // remix doesn't do non-standard search param parsing,
  // you use the built in URLSearchParams object
  let query = url.searchParams.get("q");
}
 
export function action({ request }) {
  // formData is part of the web fetch api
  let formData = await request.formData();
}

Remixを学び始めると、Remixドキュメント以上に、MDNのドキュメントに多くの時間を費やすことになるでしょう。Remixがあなたがそれを使用していない場合でも、より良いウェブサイトの構築を支援することを望んでいます。

Remixを上達させることで、ウェブのスキルも自然と向上します。

これは私たちにとっての核心的な価値です。Remixアプリは非常に高速ですが、私たちは実際にはパフォーマンスに過度に焦点を当てておらず、ユーザーと開発者の体験に焦点を当てています。Remixでは、問題の解決策を見つけるためにプラットフォームを参照し、それらをより使いやすくすることで、パフォーマンスは自然と改善されます。

変化に最適化する

両方のフレームワークがどのように動作するかを知ったので、アプリが変化にどのように応答するかを見てみましょう。私は常に「変化を最適化する」というフレーズが好きで、RemixのAPIを設計する際にはよく話題にしています。

ホームページの変更

ホームページの商品を変更したいと考えてみましょう。その場合、どのようになるでしょうか?Next.jsには2つの選択肢があります。

  • アプリを再ビルドして再デプロイします。ストア内の商品数が増えるにつれて、ビルド時間は増加します(各ビルドはすべての商品のデータをShopifyから取得する必要があります)。フッターのタイプミスを修正するだけでも、その変更をデプロイするためにShopifyからすべての商品をダウンロードする必要があります。ストアが数千の商品に成長すると、これは問題になります。

  • Incremental Static Regenerationを使用します。- VercelはSSGのビルド時間の問題に認識したので、ISRを作成しました。ページがリクエストされると、サーバーはキャッシュされたバージョンを送信し、その後バックグラウンドを用いて新しいデータで再構築します。次の訪問者は新しくキャッシュされたバージョンを取得します。

    ページがデプロイ時にビルドされていなかった場合、Next.jsはページをサーバーレンダリングし、その後CDNにキャッシュします。これはまさにHTTPのstale-while-revalidateと同じですが、ISRは非標準のAPIとベンダーロックインが付属しています。

Remixでは、単にShopifyで製品を更新するだけで、キャッシュのTTL内で製品が更新されます。また、午後にウェブフックを設定してホームページのクエリを無効化することもできます。

このインフラストラクチャはSSGを選択するよりも手間がかかりますが、この記事で見たように、任意の量の製品カタログ、任意の種類のUI(検索ページ)にスケール可能になり、より多くのユーザーにおいてSSGよりも高速になります(一般的な検索クエリをキャッシュできます)。また、特定のホストに制限されず、Remixはアプリケーションロジックにほとんど標準のWeb APIを使用しているため、フレームワークにほとんど制限されることもありません。

さらに、データをサーバー上での読み込みのみで考えることにより、クリーンな抽象化につながると考えています。

キャッシュミスについてはどうですか?

これは素晴らしい質問です。サーバーとHTTPのキャッシュは、サイトにトラフィックがある場合にのみ機能します。実際、ビジネスも同様に、サイトにトラフィックがある場合にのみ機能します 😳。1日に2回のページビューで1秒速くなる必要はありません。むしろ、メーリングリストが必要です。

  • Remixの製品ページでのキャッシュヒットは、Next.jsサイトの検索ページ(SSGを使用できない場合)よりも遅くありません。あなたはいつオンラインショッピングをしたときに検索せずに商品を購入しましたか?よく使用されるクエリでキャッシュが満たされると、さらに高速になります。
  • よく利用されるランディングページはほぼ常にキャッシュを使用可能となり、Remixのプリフェッチングにより次のページへの遷移が即座に行われるようになります。Remixは、Next.jsとは異なり、動的またはそれ以外の任意のページをプリフェッチできます。
  • SSGではある特定のスケール以降は、ISRに切り替える必要があります。これにより、前回のデプロイに含まれていなかったページでも同じキャッシュミスの問題が発生します。

キャッシュミスのリクエストが訪問の大部分を占める場合、キャッシュヒット率を100%にすることはビジネス上の問題を解決しません。それは技術的な問題ではなく、マーケティングの問題です。

パーソナライゼーション

別の変更を考えてみましょう。プロダクトチームがあなたにやってきて、ユーザーが過去に購入した製品と似た製品を表示するようにホームページが変更されると言います。

検索ページと同様に、SSGは不可能であり、デフォルトではパフォーマンスも失われます。SSGは実際には限られたユースケースしか持っていません。

ほとんどのウェブサイトにはユーザーがいます。サイトが成長するにつれて、ユーザーに対しよりパーソナライズされた情報を表示するようになります。その場合、毎回クライアント側でのフェッチが行われます。ある時点で、ページの大部分がクライアント側でフェッチされ、パフォーマンスが大幅に低下します。

Remixでは、この場合はバックエンドでの異なるデータベースクエリであるというだけです。

Eコマースの食物連鎖の頂点であるAmazon.comを考えてみましょう。ページ全体がパーソナライズされています。結果は最初からわかっています。ホームページを調整する際に手放す必要のあるものではなく、目指すべき方向性を可能にしてくれるアーキテクチャに投資してください。

要点

Remixのシンプルな <Form> + action + loader のAPIと、サーバー上にできるだけ多くのものを留めるための設計の効果を見落しがちですが、これは非常に革新的なことです。

これらのAPIは、Remixの高速なページロード、高速な遷移、ミューテーション周りのより良いUX(中断、競合状態、エラー)、そして開発者にとってよりシンプルなコードを実現します。

Remixアプリは、バックエンドのインフラストラクチャとプリフェッチングによって高速化されます。Next.jsはSSGから高速化を得ています。しかし、SSGはユースケースが限られており、特に機能やデータのスケールが進むにつれて、その速度を失ってしまいます。

SSGとJamstackは、遅いバックエンドサービスの回避策として優れていました。最新世代のプラットフォームとデータベースは高速であり、今後ますます高速化しています。これらのアプリをバックアップするShopify APIでも、ほぼ世界中のどこからでも200msでクエリのレスポンスを送信できます。私は南極大陸以外のすべての大陸からテストしました!(今月、南極に行く予定のある@chancethedevに試してもらう必要があります。)

この記事で議論されているキャッシュ戦略はすべてスキップしても、サーバー上の各リクエストでShopify APIにアクセスしても正直大丈夫だと思います。1.2秒の読み込み時間が1.4秒になり、0.8秒の場合は1秒になります。ほとんど変わりません。もしバックエンドAPIが遅い場合は、バックエンドを高速化するために時間を投資してください。それが制御できない場合は、自分自身のサーバーをデプロイし、すべてのユーザーのページを高速化するためにキャッシュを行ってください。

バックエンドへの投資はSSGと同じパフォーマンス結果をもたらしますが、どのような種類のページにも対応することができます。SSGよりも初期の作業量は多くなりますが、長期的にはユーザーとあなたのコードのためにはやるに値すると考えています。

データの読み込みは物語の半分に過ぎません。Remixでは、データの抽象化はデータの変更もカプセル化できます。すべてのコードはサーバー上にあり、アプリケーションコードが改善され、ブラウザ上のバンドルも小さくなります。

Next.jsでは、APIルートとUIの他の部分への更新の伝播のために、ブラウザにデータの変更コードを自分で送る必要があります。この記事で見たように、トップチームでもエラーや中断、競合状態の周りでこれをうまく処理できないことがあります。

"getServerSidePropsを無視しているのではありませんか?"

一部の人々は、getServerSidePropsでRemixが行っていることをすべて実現できると言っています。この質問は、まだRemixを十分に説明する機会がないために生じています!

前述のように、これは確かに検索ページの読み込みを高速化します。ただし、データの変更にはまだ対応する必要があります。変更(エラーハンドリング、中断、競合状態、リダイレクト、再検証を含む)のために、getServerSideProps、APIルート、およびそれらと通信する独自のブラウザコードの組み合わせが必要です。ここで言いたいことは、「自分自身のRemixを構築することもできる」ということです。もちろん、それは可能です。実際、私たちはすでにそれを行っています 😇。

ふぅ!

私たちによく尋ねられる大きな質問に答えたので、今後の投稿ではRemixができることを見せていくことができます!