SPAの先へ:高パフォーマンスなアプリ構築にRemix.jsが最適な理由

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

はじめに:60秒で本番環境レベルの雛形を作成

Remixを試すまで、私は長年状態管理ライブラリとの格闘に明け暮れていました。Remixは複雑な抽象化の代わりに、HTTP、HTML、ブラウザネイティブの動作といったWeb本来の設計図を採用しています。多くのフレームワークがWebを抽象化の層に埋もれさせてしまう一方で、Remixはそれを最大限に活用することを選択しました。最新のプロジェクトでは、CLIを使って1分足らずでブートストラップを完了させました。

npx create-remix@latest

インストーラーでは、Vercel、Cloudflare、Fly.ioなどのデプロイ先を選択するよう求められます。ほとんどのプロジェクトでは、デフォルトのRemix App Serverが堅牢な出発点となります。インストールが完了すると、クリーンなプロジェクト構造が作成されます。app/ディレクトリはコマンドセンターとして機能し、構築するすべてのルート、スタイル、コンポーネントがここに格納されます。

ロードマップ:論理的なファイルベースルーティング

app/routes/ディレクトリを、アプリケーションのロードマップと考えてください。Remixはファイルベースルーティングを採用しているため、ファイル名がそのままURLパスになります。app/routes/dashboard.tsxに作成されたファイルは、即座に/dashboardで公開されます。この予測可能なマッピングにより、特定のビューやロジックブロックを探す際も、大規模なコードベースを管理しやすい状態に保てます。

コアアーキテクチャ:Loader and Action

Remixへの移行において、データ処理は最大の考え方の転換点となります。多くのReactアプリの速度を低下させている「useEffect-fetch」のウォーターフォールの混乱から、ようやく解放されるのです。Remixは、loaderactionという2つの主要な関数を導入することで、これを簡素化します。

Loader:サーバーサイドの門番

Loaderはサーバーサイドの門番です。これらはサーバー上でのみ実行されるため、データベースへのクエリや外部APIの呼び出しを安全に行うことができます。クライクライアントにこのコードが見えることはないため、プライベートな環境変数を安心して使用できます。最近のプロジェクトでは、データフェッチをloaderに移したことで、初期バンドルサイズを15%削減し、LCP(Largest Contentful Paint)を450ms改善しました。

import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { getPosts } from "~/models/post.server";

export const loader = async () => {
  const posts = await getPosts();
  return json({ posts });
};

export default function Posts() {
  const { posts } = useLoaderData<typeof loader>();
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.slug}>{post.title}</li>
      ))}
    </ul>
  );
}

サーバー上でデータを取得することで、不快なレイアウトシフトが解消されます。ユーザーは即座にデータが埋め込まれたHTMLドキュメントを受け取ります。これはSEOにとって有益なだけでなく、重いJSバンドルの実行に5秒かかるような低速な3G回線でもサイトを利用可能にします。

Action:ユーザーの意図を処理する

Loaderが読み取りを担当するなら、actionsは書き込みを管理します。これらは標準のHTMLフォームを使用して、POST、PUT、DELETEリクエストを処理します。ユーザーが送信ボタンを押すと、Remixはサーバー上でリクエストを処理します。その後、アクティブなすべてのloaderの再検証を自動的にトリガーします。これにより、手動の状態管理なしで、UIをデータベースと完全に同期させることができます。

export const action = async ({ request }: ActionFunctionArgs) => {
  const formData = await request.formData();
  const title = formData.get("title");
  // データベースのロジックをここに記述
  return redirect("/posts");
};

高度なパターン:ネストされたルーティングとOptimistic UI

ネストされたルーティングは、Remixの隠れたスーパーパワーです。これは単にUIの見栄えの問題ではなく、データの流れに関わるものです。Remixは、アクティブな複数のルートセグメントのデータを同時かつ並列に取得できます。

なぜネストされたルートが重要なのか

サイドバーとメインコンテンツエリアを持つダッシュボードを考えてみましょう。従来のフレームワークでは、サイドバーのリンクをクリックすると通常、ページ全体の再読み込みが発生するか、複雑なグローバル状態の更新が必要になります。Remixでは、特定のネストされたルートセグメントのみが変更されます。この正確な処理により、一部のビューではデータ転送量を最大70%削減でき、遷移を瞬時に感じさせることができます。

Optimistic UIの実装

ローディングスピナーはUXの敗北です。RemixはOptimistic UIを実装するためのuseFetcherフックを提供しています。これにより、サーバーリクエストがすでに成功したかのようにインターフェースを更新できます。最終的にサーバーがエラーを返した場合、Remixがロールバックを処理してくれます。3,000マイル離れたデータベースと通信していても、アプリはローカルのデスクトップツールのように感じられます。

パフォーマンスと本番環境のベストプラクティス

アプリを構築するのは最初の一歩に過ぎません。Remixにおける真のパフォーマンスは、JavaScriptを重ねるのではなく、Webプラットフォームの組み込みツールを活用することから生まれます。

キャッシュは最良の友

Remixでは、HTTPキャッシュヘッダーの設定が非常に簡単です。任意のルートからheaders関数をエクスポートして、ブラウザやCDNにコンテンツの保存方法を指示できます。これはアクセスの多いブログやECサイトにとって極めて重要です。動的なサーバーの力を持ちながら、静的サイトのようなスピードを手に入れることができます。

export const headers = () => ({
  "Cache-Control": "public, max-age=3600, s-maxage=86400",
});

Error Boundaryによる回復力

1つのコンポーネントの破損でアプリケーション全体をクラッシュさせるべきではありません。RemixはError Boundaryでこの問題を解決します。任意のルートに対してErrorBoundaryを定義できます。ネストされたルートが失敗した場合、その特定のセクションのみにエラーメッセージが表示されます。アプリケーションの残りの部分は完全に操作可能なままであり、これは複雑でデータ量の多いダッシュボードにおいて大きな利点となります。

export function ErrorBoundary() {
  return (
    <div className="error-container">
      <h2>エラーが発生しました。</h2>
      <p>ダッシュボードの他の部分は引き続き利用可能です。</p>
    </div>
  );
}

アプリのデプロイ先を選ぶ

公開の準備ができたら、ニーズに合わせてデプロイ先を選択しましょう。低レイテンシを優先するなら、Cloudflare Workersは優れたエッジコンピューティング環境を提供します。データベースを多用するアプリなら、Fly.ioがおすすめです。マルチリージョンデプロイとSQLiteをサポートしており、Remixのサーバー中心の性質を完璧に補完します。

クライアントサイドのSPAからサーバー重視のフレームワークへの乗り換えには、視点の転換が必要です。しかし、その結果は疑いようがありません。ロード時間の短縮、よりクリーンなコード、そしてWeb本来の強みに根ざした開発体験が得られるのです。

Share: