🏝️

TanStack Routerという選択肢

2024/10/20に公開

TanStack Routerを使った型安全なルーティング

Next.jsやRemixなどのフレームワークを使用するほどではないけど、SPAでルーティングを使いたい時がありTanStack Routerを採用してみました。
個人的にはフレームワークを使用しないのであれば良い選択肢の一つだなと思いましたので良かった点をまとめてみます

公式ドキュメント

1. 型安全なルーティング

ファイルベースのルーティングは確かに便利ですが、型安全性の観点からは不安が残る部分もあります。TanStack Routerでは、ルーティングに型を付けることができるため、型安全にルーティングを実装できます。

これは2024/10/20現在Next.jsでもexperimentalな機能として提供されていますが、使用感はTanStack Routerのほうが優れていると感じました

2. .lazyでバンドル分割

https://tanstack.com/router/latest/docs/framework/react/guide/code-splitting#using-the-lazytsx-suffix

TanStack Routerでは、ファイル名に.lazyを付けることで、ページごとにバンドルを分割できます。たとえば、routes/about.lazy.tsxの形式で指定すると、そのページはアクセス時に初めて読み込まれます。これはReactのlazy importと同様の仕組みです。

3. loaderでデータ取得

https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#route-loaders

TanStack Routerには、ページにアクセスした際にデータを取得するためのloader APIが組み込まれています。loaderを使うことで、データを事前に取得し、それに基づいてページを描画できます。

import { createFileRoute } from "@tanstack/react-router";

async function loaderFn() {
  await new Promise((res) => {
    setTimeout(res, 1000);
  });
  return "hoge";
}

export const Route = createFileRoute("/posts/$postId")({
  loader: async () => {
    const data = await loaderFn();
    return {
      data,
    };
  },
  component: Post,
});

import { Await, createFileRoute, defer } from "@tanstack/react-router";

async function loaderFn() {
  await new Promise((res) => {
    setTimeout(res, 10000);
  });
  return "hoge";
}

export const Route = createFileRoute("/posts/$postId")({
  loader: async () => {
    const data = loaderFn();
    const data2 = await loaderFn();
    return {
      data: defer(data),
      data2,
    };
  },
  component: Post,
});

function Post() {
  const { data } = Route.useLoaderData();
  return (
    <div>
      <h1>Post</h1>
      <span>{data}</span>
    </div>
  );
}

ただし、loaderの処理が完了するまでページの描画が行われないため、待機中のコンポーネントを表示したい場合にはpendingComponentを指定できます。

export const Route = createFileRoute("/posts/$postId")({
  loader: async () => {
    const data = await loaderFn();
    return {
      data,
    };
  },
  pendingComponent: () => <div>loading...</div>,
  component: Post,
});

こうすることで、loaderが終わるまでpendingComponentが表示されるようになります
ただ、loaderで取得したデータが必要な部分以外は先に表示しておきたい場合もあります
そのときは次に紹介するdeferAwaitコンポーネントを使用して解決できます

4. deferAwaitコンポーネント

https://tanstack.com/router/latest/docs/framework/react/guide/deferred-data-loading#deferred-data-loading-with-defer-and-await

ページ全体の描画を待たずに、必要なデータが揃っていない部分以外を先に表示したい場合、deferAwaitコンポーネントを使うことで柔軟に対応できます。

import { Await, createFileRoute, defer } from "@tanstack/react-router";

async function loaderFn() {
  await new Promise((res) => {
    setTimeout(res, 10000);
  });
  return "hoge";
}

export const Route = createFileRoute("/posts/$postId")({
  loader: async () => {
    const data = loaderFn();
    return { data: defer(data) };
  },
  component: Post,
});

function Post() {
  const { data } = Route.useLoaderData();
  return (
    <div>
      <h1>Post</h1>
      <Await promise={data} fallback={<div>loading...</div>}>
        {(data) => <div>{data}</div>}
      </Await>
    </div>
  );
}

この方法を使うことで、ページ全体を表示しつつ、データの取得が完了するまでは指定したfallbackコンポーネントが表示されます。

まとめ

今回使用したのは小規模なツール開発だったため、ルーティングライブラリとしてTanStack Routerを採用しましたが、より大規模なサービス開発では、Next.jsやRemixのようなSSRやRSCなどのサーバーサイド機能を備えたフレームワークの方が、パフォーマンスやバンドルサイズの面で優れていると思います。

ユースケースによっては、TanStack Routerも選択肢の一つとして検討する価値があると感じました。

他にも機能を兼ね備えているので、いろいろ使用してみたいと思います

Discussion