Remix.js: Web Standardsに準拠したWebアプリ構築 — Data LoadingからServer Actionsまで

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Remix.js: 新しい風か、それとも本質的な価値への回帰か?

ReactでSingle Page Application (SPA)を作ったことがあるなら、ページ遷移のたびにローディングスピナーが回り続けるのを待った経験があるでしょう。通常、データはコンポーネントのマウント後にクライアント側でフェッチされます。この手法は、特にカフェの弱いWi-Fiや高速移動中のネットワーク環境では、ユーザー体験に「断絶」を生じさせます。

私はかつて、useEffectの扱いやレースコンディションの防止、複雑な状態管理ライブラリの設定に何週間も費やしていました。しかし、Remix.jsを試したことで全てが変わりました。Remixは複雑な概念を次々と生み出すのではなく、強力でありながら忘れられがちな「Web Standards(Web標準)」を徹底的に活用するという、あえて時代を逆行する道を選んでいます。

Remixの核となる哲学は Progressive Enhancement(漸進的機能向上) です。簡単に言えば、JavaScriptでエラーが起きたり、読み込みが完了していなくても、アプリケーションが動作し続ける必要があるということです。このアプローチはレスポンスを高速化するだけでなく、開発者が軽視しがちな「SEO」にも非常に効果的です。

Remixは、サーバー上で並列実行される loader を使うことで、「ウォーターフォール型フェッチ(リクエストの連鎖)」を完全に解消します。機密性の高いAPIキーの漏洩を心配したり、重いデータ処理ロジックをユーザーのブラウザに詰め込んだりする必要はもうありません。

プロジェクトの初期化:迅速かつスマートに

始める前に、Node.js v18以上がインストールされていることを確認してください。次のコマンド1つで全てをセットアップできます。

npx create-remix@latest my-remix-app

インストール時には、開発をスムーズに進めるために以下の選択肢を推奨します:

  • Directory: プロジェクト名を明確にする(例:remix-ecommerce-v1)。
  • Install dependencies: Yes を選択(npmやpnpmを使用して迅速にインストール)。
  • TypeScript: 常に Yes を選択。Remix의 loader からコンポーネントへの自動型推論は非常に強力で、つまらない undefined エラーを防いでくれます。
cd my-remix-app
npm run dev

アプリケーションは localhost:3000 で起動します。Remixのディレクトリ構造は非常に直感的で、ほとんどの作業はルートと主要なロジックが含まれる app/ ディレクトリ内で行います。

Loader、Action、Progressive Enhancementを使いこなす秘訣

これら3つがRemixを特別なものにしています。実際の製品一覧ページを例に見てみましょう。

1. Data Loading + Loader(サーバーサイド・フェッチ)

Remixの各ルートファイルは、小さなAPIエンドポイントのような役割を果たします。loader 関数はサーバー上でのみ実行されるため、中間API層を介さずにデータベースへ直接クエリを投げることができます。

// app/routes/products.tsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

export const loader = async () => {
  // DBからデータを取得(例:最新の10件)
  const products = [
    { id: 1, name: "Akko メカニカルキーボード", price: 1200000 },
    { id: 2, name: "Logitech G502 マウス", price: 850000 },
  ];
  return json({ products });
};

export default function ProductsPage() {
  const { products } = useLoaderData<typeof loader>();
  
  return (
    <div>
      <h1 className="text-2xl font-bold">ホットな商品</h1>
      <ul>
        {products.map(p => (
          <li key={p.id}>{p.name} - {p.price.toLocaleString()} VND</li>
        ))}
      </ul>
    </div>
  );
}

実務での経験から言うと、ShopifyやContentfulからの多層構造のJSONを扱う際、データが混乱しがちです。私はよく JSON Formatter を使ってデータを整理し、TypeScriptのインタフェースを定義する前に構造を確認しています。これでデバッグ時間を少なくとも15分は短縮できます。

2. Server Actions によるデータ更新

面倒な useStateonSubmit は忘れてください。Remixは私たちを、より強力になった「聖なる <Form> タグ」へと連れ戻してくれます。送信ボタンが押されると、サーバー上の action 関数がデータを受け取り処理します。

// app/routes/products/new.tsx
import { redirect, type ActionFunctionArgs } from "@remix-run/node";
import { Form } from "@remix-run/react";

export const action = async ({ request }: ActionFunctionArgs) => {
  const formData = await request.formData();
  const name = formData.get("name");
  const price = Number(formData.get("price"));

  // ここでデータベースに保存
  console.log("Saving product:", { name, price });

  return redirect("/products");
};

export default function NewProduct() {
  return (
    <Form method="post" className="space-y-4">
      <input type="text" name="name" placeholder="商品名" required />
      <input type="number" name="price" placeholder="価格" required />
      <button type="submit">商品を保存</button>
    </Form>
  );
}

最も素晴らしい点は、action が完了すると、Remixが自動的にページ全体のデータを「再検証(re-validate)」することです。状態同期のコードを一行も書かずに、商品一覧を最新の状態に更新できます。

3. Progressive Enhancement の実践

実験してみましょう。ブラウザのJavaScriptをオフにして、上記のフォームを送信してみてください。驚くほどスムーズに動作するはずです! JSがある時、Remixは機敏なSPAとして動作します。JSがない時は、伝統的なHTMLフォームの仕組みに自動的にフォールバックします。これが、どんなネットワーク条件下でも揺るがない「堅牢な」アプリの作り方です。

運用と監視:アプリケーションの健全性を維持するために

Remixのデプロイは、サーバー環境(Node.js、Bun、またはEdge Functions)が必要なため、純粋なReactアプリとは異なります。

ErrorBoundary によるスマートなエラー処理

Remixは小さなエラーでページ全体をクラッシュさせません。ErrorBoundary を使えば、子ルートでエラーが発生してもその部分だけがエラーメッセージに置き換わり、他の部分は正常に動作し続けます。

export function ErrorBoundary() {
  return (
    <div className="p-4 bg-red-50 text-red-700">
      <h2>システムが一時的に混み合っています</h2>
      <p>ページを更新するか、数分後にもう一度お試しください。</p>
    </div>
  );
}

監視すべき指標

ロジックの多くがサーバーにあるため、loader の速度が鍵となります。以下のツールに注目しましょう:

  • Vercel/Cloudflare Analytics: Real User Metrics (RUM) を追跡し、実際のユーザーが体感している速度を把握します。
  • Sentry: ユーザーが不満を抱く前に、サーバーサイドで500エラーをキャッチします。
  • Lighthouse: LCP指標に集中してください。RemixではLCPが1.2秒以下になることも珍しくなく、これは従来のSPAでは夢のような数字です。

ヒント:サードパーティAPIの結果は常にRedisでキャッシュしましょう。loader のレスポンスに500ms以上かかると、ユーザーはルート間の切り替え時に「もたつき」を感じるようになります。

Remixを学ぶことは、単に新しいツールを学ぶことではありません。より速く、より安全で、より堅牢な「プロフェッショナルなWeb製品」の作り方を再定義することなのです。

Share: