4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.jsとTanStack Startの、ルーターの型安全な部分を比較する

4
Last updated at Posted at 2026-02-08

はじめに

普段はNextJSを使っている者ですが、先週こちらの記事の内容を見て、TanStack Startについて気になったので、その開発体験をしてみました。

今回の記事では、それぞれのルーティングを使う際の利点と注意点を見ていきます。

ルーティング思想と書き方の違い

  • Next.js: ファイルシステムベース。URLはフォルダ構成
  • TanStack: URLは一つの巨大な型定義

例として、以下のページの動的なルーティングの仕方を考えます。

  • users
  • users/{id}

NextJSの場合

app/
 ├── users/
 │    └── page.tsx        // /users
 └── user/
      └── [id]/
           └── page.tsx   // /user/123

実装のコード(詳細ページ)

Next.js 15.5からは Route Props Helpers を使うことで、以下のように書けます。

.tsx
// app/user/[id]/page.tsx
export default async function UserPage({ params }: PageProps<'/user/[id]'>) {
  const { id } = await params;
  
  // ディレクトリ構成を読み取って型定義が自動生成されるため、
  // PagePropsのジェネリクスでは存在するパスのみ指定可能で安全。
  return <div>User ID: {id}</div>;
}

TanStack Start の場合

TanStackでは、まず「このパスには $id という変数が存在する」という契約(Route定義)をコードで記述します。

routes/
 ├── users.tsx           // /users
 └── user.$id.tsx        // /user/123
.tsx
// routes/user.$id.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/user/$id')({
 component: UserComponent,
})

function UserComponent() {
 // useParams を呼ぶだけで、すでに string 型の id が補完される。
 // もしパス定義を /user/$userId に変えたら、ここはエディタがエラーを通知する。
 const { id } = Route.useParams() 
 
 return <div>User ID: {id}</div>
}

Next.jsとTanStackStartは、どちらもパスとコードの一貫性を保つ書き方をすることで、
パスを変えたのに、コードのPathParamsを変え忘れる、ということが起こらないようになります。

<Link> コンポーネント

<Link> コンポーネントも、Next.jsの typedRoutes を利用する場合の挙動と、TanStackStartのデフォルトの挙動で似たような動きをします。

.ts
// next.config.ts(Next.jsの設定)
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  typedRoutes: true, // これを追加すると<Link>コンポーネントのパスをチェックする
};

export default nextConfig;
.tsx
// NextJSの<Link>
<Link href="/user/123">プロフィールへ</Link> // /user/123は存在するパスなのでOK
<Link href="/users/123">プロフィールへ</Link> // /users/123は存在しないのでNG
.tsx
// TanStackの<Link>
<Link to="/user/$id" params={{ id: '123' }}>プロフィールへ</Link>
// 1. `to` に存在しないパスを入れようとするとエディタに怒られる
// 2. paramsが[id]なのか、[userId]なのかで、Route定義と一致する必要がある

両方とも存在しないパスにはエラーを表示しますが、

URLの扱いはStringがメインのNext.jsと、toParams に分解されているTanstackStartで違いがあります。

toParamsに分かれている利点は、コードを書くときの補完(どのパスにどのパラメータが必要なのか)や、リファクタリングの場面で地味に生きてきそうです。

QueryParams

例えば以下のようなクエリパラメータがある場合、

  • users/{id}?tab={postsまたはlikes}

Next.jsではPageProps + zod でコンポーネント内ではQueryParamsが型定義できますが

.tsx
import { z } from 'zod';

// クエリパラメータのバリデーションスキーマ定義
const userSearchSchema = z.object({
  tab: z.enum(['posts', 'likes']).default('posts'),
});

export default async function UserPage({ params, searchParams }: PageProps<'/user/[id]'>) {
  const { id } = await params;

  // tabにはpostsかlikesしか来ない
  const rawSearchParams = await searchParams;
  const { tab } = userSearchSchema.parse(rawSearchParams);

  return (
    <div>
      <h1>ユーザーID: {id}</h1> {/* id は string */}
      <p>現在のタブ: {tab}</p>   {/* tab は 'posts' | 'likes' */}
    </div>
  );
}

Next.jsの <Link> ではクエリパラメータのバリデーションが効かないため、
パスの間違いは検知されているけど、クエリパラメータが間違っている部分は指摘されません。

CleanShot 2026-02-13 at 16.54.17.png

TanStackの場合、Routeを定義するときにvalidateSearchを設定すると、
その型を <Link> コンポーネントにも強制できます。

.tsx
// routes/user.$id.tsx
export const Route = createFileRoute('/user/$id')({
  validateSearch: (search) => z.object({
    tab: z.enum(['posts', 'likes']).default('posts'),
  }).parse(search),
  
  component: UserComponent,
})

function UserComponent() {
  const { id } = Route.useParams() 
  
  // クエリパラメータの tab は「検証済みの型」として取得
  const { tab } = Route.useSearch() 

  return (
    <div>
      <h1>ユーザーID: {id}</h1>
      <p>現在のタブ: {tab}</p>
    </div>
  )
}

これに対してLinkを色々書いてみると、クエリパラメータの部分も、
エディタにエラーが表示されます。

CleanShot 2026-02-08 at 13.44.48@2x.png

TanStackの注意点

エディタにエラーは出ているのですが、
このままでもTanStackはデフォルトでビルドを通しちゃうみたいです。

CleanShot 2026-02-08 at 14.14.41@2x.png

型チェックをするため、package.jsonを修正する必要がありました。

"build": "tsc && vite build",

CleanShot 2026-02-08 at 14.16.47@2x.png

これによって、<Link>コンポーネントの安全性 + ビルド時の型チェックも対応できました。

まとめ

  • Next.js は、PagePropstypedRoutes を使うことで、パスパラメータの定義や <Link> コンポーネントの安全性が上がりました
  • 一方で、クエリパラメータにおいては、TanstackStart のガードレールが優秀でした
  • 双方適切な設定を行い、ルーターを安全に利用しましょう
4
5
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?