19 人が閲覧(直近 30 日) React

TanStack Startとは?1.0安定版の変更点と使い方・SEO設定・Bun/Dockerデプロイ【1.169】

TanStack Startとは?1.0安定版の変更点と使い方・SEO設定・Bun/Dockerデプロイ【1.169】

TanStack Startは、TanStack Routerを土台にしたReactのフルスタックフレームワークです。2026年10月13日に公式ブログが「TanStack Start 1.0 is Stable」を告知し、@tanstack/react-start 1.169が最初の安定版になりました。RC期の解説記事に載っている .inputValidator() は安定版で削除されており、Vite 7のままではビルドできません。この記事では、1.169.4(2026年10月22日公開)を前提に、RCから変わった点、Server FunctionsとServer Routesの書き方、SEO設定、Bun・Dockerへのデプロイ、2026年に出たセキュリティ勧告までをまとめます。

まとめ:TanStack Start 1.0の要点

  • 現状:1.169が最初の安定版。最新は @tanstack/react-start 1.169.4、Node.js 22.12.0以上、Vite 8以上(またはRsbuild 2)が必要。
  • RCからの変更:サーバー関数の .inputValidator() は .validator() へ。useSession と @tanstack/nitro-v2-vite-plugin も削除。
  • 始め方:npx @tanstack/cli@latest create。公式の設定例は開発サーバーを3000番で起動する。
  • サーバー処理:アプリ内から呼ぶならServer Functions、外部から叩くAPIならServer Routes。サーバー関数は既定でクロスサイトのリクエストを403で拒否する。
  • SEO:ルートの head() でtitle・meta・canonical・JSON-LDを出し、プリレンダリングを有効にすればサイトマップも自動生成できる。
  • デプロイ:Cloudflare Workers・Netlify・Railwayが公式パートナー。Vercel・Node.js・DockerはNitro経由、Bunは専用手順(React 19が必要)。
  • 注意:React Server Componentsはまだ実験的機能。2026年9月30日公開の反射型XSS(CVE-2026-102989)は1.168.60未満が対象なので、古い版のままの本番環境は更新が必要。

TanStack Startの位置づけ:Routerに足されるサーバー機能

公式Overviewは、TanStack Startのルーティングが「100% TanStack Router」に依存すると明記しています。型安全なルート定義、検索パラメータの検証、loaderによるデータ取得はRouterの機能で、Startが足すのはドキュメント全体のSSR、ストリーミング、Server Functions、Server Routes、ミドルウェア、クライアントとサーバーの両方のビルドです。ビルドツールはViteかRsbuildを選べ、Rsbuild対応は2026年6月2日の公式ブログで発表されました。

TanStack Router単体で足りるケース

公式はSSRもサーバー関数も要らないと「確実に分かっている」ならRouter単体を検討してよいとしています。管理画面のようにログイン後しか表示しないSPAで、APIは別のバックエンドにあるならRouter単体で十分です。Routerの型安全ルーティングやloaderの既定値はTanStack Routerの解説記事で詳しく扱っています。検索エンジンに本文を読ませたいページがある、フロントと同じリポジトリにAPIを置きたい、のどちらかに当てはまるならStartを選びます。

1.0安定版までの経緯と版番号

TanStack StartはRouterと同じ1.x系の版番号で公開されているため、「1.0」と「1.169」が同じものを指します。公式のMigrate from the Release Candidateは、1.169が最初の安定版だと書いています。

日付 出来事 版
2025-09-23 v1 RC(リリース候補)を告知 1.132.0
2026-06-02 Rsbuild 2への対応を発表 1.168系
2026-10-12 npmに安定版を公開 1.169.0
2026-10-13 公式ブログで1.0安定版を告知 1.169系
2026-10-22 執筆時点の最新版 1.169.4

日付と版はnpmレジストリの公開時刻と公式ブログ「TanStack Start 1.0 is Stable」で確認したものです。Solid版の @tanstack/solid-start も latest は1.169.4ですが、別に2.0.0のRC(rcタグ)が並行して公開されています。

RC期の記事のコードが動かない理由:1.169で変わった点

RC期以前の記事を参照する場合は、使用しているAPIとビルドツールの版を確認してください。削除されたAPIを含むコードは、そのままでは動きません。移行ガイドが挙げる主な変更は次のとおりです。

RC期まで 1.169以降
.inputValidator() .validator()(渡す検証関数は同じ)
Vite 7 Vite 8が必須
@tanstack/nitro-v2-vite-plugin nitro/vite の nitro()
useSession iron-sessionなど外部のセッションライブラリ

検証環境(@tanstack/react-start 1.169.4・tsc 7.0.2)で .inputValidator() を使ったサーバー関数を型検査すると、error TS2339: Property 'inputValidator' does not exist on type 'ServerFnBuilder<Register, "GET", true>' で止まりました。ビルドスクリプトが vite build && tsc --noEmit の構成ならビルドも失敗します。useSession の置き換えには注意が要ります。移行ガイドは、新しいライブラリが既存のStartのセッションCookieを読めないため、Cookie名を変えて再ログインさせるか、旧Cookieを読む移行処理を試すよう求めています。

エラーの扱いも変わりました。サーバー関数の呼び出しは投げられた値でそのままrejectされ、setResponseHeader('set-cookie', cookie) は送信予定のCookieをすべて置き換えます。Cookieを1つ足すときは setCookie を使います。なお、古い記事に出てくる app.config.ts と Vinxi は現行ドキュメントに登場せず、設定は vite.config.ts に tanstackStart() プラグインを入れる形です。

インストールと起動:CLI・ポート・ディレクトリ構成

公式のGetting Startedは、ブラウザで構成を選ぶTanStack Builderを第一の方法に挙げ、ローカルで作る場合はTanStack CLIを案内しています。npmの @tanstack/react-start のpackage.jsonは engines を node >=22.12.0、peerDependenciesのViteを >=8.0.0 と定めているため、Node.js 20はサポート対象外です。LTSの切り替え時期はNode.js LTSの版と移行手順を見てください。

npx @tanstack/cli@latest create
cd my-app
npm run dev

対話形式でパッケージマネージャーとTailwind CSS・ESLintなどの追加機能を選びます。完成済みの公式サンプルから始めるなら npx gitpick TanStack/router/tree/main/examples/react/start-basic start-basic で複製できます。Viteそのものの既定ポートは5173ですが、公式サンプルとBuild from Scratchの設定は次のように3000を指定しています。

// vite.config.ts(最小構成の例。生成済みプロジェクトのTailwind CSSプラグインやresolve設定は削除しない)
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'
import { nitro } from 'nitro/vite'

export default defineConfig({
  server: { port: 3000 },
  plugins: [tanstackStart({ srcDirectory: 'src' }), viteReact(), nitro()],
})

ポートを変えたいときは server.port を書き換えます。プラグインの並びとVite 8.3の変更点はVite+Reactの環境構築手順が参考になります。ルートは src/routes のファイル構成がそのままURLになり、routeTree.gen.ts が自動生成されます。

src/
├── router.tsx            // getRouter() でルーターを作る
├── routeTree.gen.ts      // 自動生成(編集しない)
└── routes/
    ├── __root.tsx        // 全ページ共通のレイアウトと <head>
    ├── index.tsx         // /
    ├── posts.$postId.tsx // /posts/123($ が動的パラメータ)
    └── api/
        └── todos.ts      // /api/todos(Server Route)

Server Functionsの書き方:createServerFnと.validator()

Server Functionsは、サーバーだけで実行される処理を、クライアントやloaderから普通の関数として呼べる仕組みです。createServerFn() の既定のHTTPメソッドはGETで、POSTにするには method を指定します。入力は .validator() に検証関数かZodなどのスキーマを渡します。この例ではZodを使うため、未導入なら npm install zod を実行します。次のコードは検証環境でビルドし、/greet へのSSRで <p id="msg">Hello, SSR!</p> が返ることを確かめたものです。

// src/routes/greet.tsx
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'

export const greetUser = createServerFn({ method: 'POST' })
  .validator(z.object({ name: z.string().min(1) }))
  .handler(async ({ data }) => {
    return { message: `Hello, ${data.name}!` }
  })

export const Route = createFileRoute('/greet')({
  loader: () => greetUser({ data: { name: 'SSR' } }),
  component: () => <p id="msg">{Route.useLoaderData().message}</p>,
})

呼び出し側は greetUser({ data: { ... } }) の形で、data の型はスキーマから推論されます。認証やログのような共通処理は createMiddleware({ type: 'function' }) で関数用ミドルウェアを作り、.middleware([...]) でつなぎます。

サーバー関数をcurlで叩くと403になる理由

サーバー関数は /_serverFn/<関数ID> というURLで公開されますが、公式ドキュメントは「同一オリジン向けのRPC」と位置づけています。src/start.ts を作っていなければ、Startは createCsrfMiddleware() を自動で入れ、Sec-Fetch-Site・Origin・Referer のいずれでも同一オリジンと確認できないリクエストを拒否します。検証環境でビルドしたサーバーに同じ関数IDへPOSTした結果は次のとおりです。

付けたヘッダー 応答
Sec-Fetch-Site: same-origin CSRF判定を通過(本文の形式違いで500)
Sec-Fetch-Site: cross-site 403 Forbidden
Origin: https://evil.example 403 Forbidden
上の3種をどれも付けない 403 Forbidden

モバイルアプリや別ドメインのフロントから呼ぶ処理をサーバー関数で書くと、この判定で弾かれます。外部から呼ぶ処理は次章のServer Routesで書いてください。src/start.ts を自分で作った場合は自動では入らないため、requestMiddleware に createCsrfMiddleware({ filter: (ctx) => ctx.handlerType === 'serverFn' }) を明示的に足し、保護対象をサーバー関数に限定します。

Server Routes(API Routes)の書き方:外部に公開するHTTPエンドポイント

Server Routesは、RC前に「API Routes」と呼ばれていた機能で、HTTPメソッドごとのハンドラーを createFileRoute() の server.handlers に書きます。Webhookの受け口、他のアプリから叩くREST API、動的なサイトマップの配信に使います。

// src/routes/api/todos.ts → /api/todos
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/todos')({
  server: {
    handlers: {
      GET: () => Response.json([{ id: 1, title: 'Learn TanStack Start' }]),
      POST: async ({ request }) => {
        const body = await request.json()
        return Response.json({ created: body }, { status: 201 })
      },
    },
  },
})

検証環境では、GETが200で content-type: application/json の配列を返し、JSONを送ったPOSTが201で {"created":{"title":"x"}} を返しました。ハンドラーは request と動的パスの params を受け取れます。

観点 Server Functions Server Routes
呼び方 関数として呼ぶ HTTPでURLを叩く
呼び出し元 同じStartアプリ 外部サービス・他アプリ
型の共有 入力と戻り値を自動推論 自分で定義
クロスサイト 既定で403 自分で制御

SEO設定:head()・サイトマップ・SSRの切り替え

公式のSEOガイドが挙げる道具は、SSR、静的プリレンダリング、ドキュメントの <head> 管理の3つです。ガイド自身が、SEOはライブラリが自動で与えるものではなく使い方次第だと書いています。

head()によるtitle・description・canonicalの設定

ルートの head にmeta配列を返すと、SSRのHTMLに反映されます。loaderの結果を使えば記事ごとに値を変えられます。

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => ({ post: await fetchPost(params.postId) }),
  head: ({ loaderData, params }) => !loaderData ? {} : ({
    meta: [
      { title: loaderData.post.title },
      { name: 'description', content: loaderData.post.excerpt },
      { property: 'og:title', content: loaderData.post.title },
    ],
    links: [{ rel: 'canonical', href: `https://example.com/posts/${params.postId}` }],
  }),
})

先の /greet に同じ形で head を足してcurlで取得すると、<title>Greet - Hello, SSR!</title> とdescriptionのmetaがサーバー応答のHTMLに入っていました。JavaScriptを実行しないクローラーにもtitleが届くということです。構造化データは scripts に type: 'application/ld+json' の要素を返して出力します。

サイトマップの生成とrobots.txtの配信

プリレンダリングでリンクをたどる設定にすると、ビルド時にサイトマップを生成できます。

tanstackStart({
  prerender: { enabled: true, crawlLinks: true },
  sitemap: { enabled: true, host: 'https://example.com' },
})

ビルド時に見つからないURL(DBから増える記事など)は、src/routes/sitemap[.]xml.ts というServer Routeで動的に返します。ファイル名の [.] はドットをそのままURLに含めるための書き方です。robots.txtは public/robots.txt に置けばそのまま配信されます。AI検索向けには、Server Routeで llms.txt を返す例が公式のGEOガイドにあります。

Selective SSRとSPAモードの違い

SSRはルートごとに切り替えられ、localStorage や canvas などブラウザ専用のAPIに頼るページで使います。以下の表は初回リクエスト時の実行場所です。ハイドレーション後の画面遷移では、loaderはクライアントでも実行されます。

設定 loaderの実行 コンポーネントの描画
ssr: true(既定) サーバー サーバー
ssr: 'data-only' サーバー クライアント
ssr: false クライアント クライアント

子ルートは親の設定を引き継ぎ、より制限の強い方向(trueからdata-onlyやfalse)にしか変えられません。全体をクライアント描画にするSPAモードは tanstackStart({ spa: { enabled: true } }) で有効にします。検索流入を狙うページに ssr: false を付けると初期HTMLから本文が消えるため、SEOが要るページでは避けてください。

ISR:Cache-ControlによるCDNキャッシュと再検証

StartのISRは、Next.jsのような専用の再生成APIではなく、標準のHTTPキャッシュヘッダーで組みます。ルートの headers に 'Cache-Control': 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400' を返すと、CDNが1時間キャッシュし、期限後は最大1日古い版を返しながら裏で取り直します。公式のStart vs Next.jsは「Cache-Controlヘッダーだけでは、どのホストでも同じ再生成や無効化が保証されるわけではない」と注意しているので、使うCDNで挙動を確認してください。

デプロイ:Nitro・Cloudflare・Netlify・Bun・Docker

公式のHostingガイドは、Cloudflare・Netlify・Railwayを公式ホスティングパートナーとしています。多くの配備先はNitroを経由する構成です。

配備先 使うもの
Cloudflare Workers @cloudflare/vite-plugin と wrangler
Netlify @netlify/vite-plugin-tanstack-start
Railway・Vercel Nitroの手順のあとGit連携
Node.js・Docker Nitroで .output/server/index.mjs を出力
Bun Nitroの preset: 'bun' か公式の server.ts
Appwrite Sites Git連携(出力先 ./.output)

Nitroそのものの仕組みとv3の位置づけはNitroの解説記事にまとめています。Hostingガイドは nitro/vite プラグインがまだ活発に開発中だと注記しており、検証環境のnitroも 3.0.260903-beta でした。

Node.jsとDocker:Nitroの.outputによる起動構成

npm run build のあと node .output/server/index.mjs で起動します。待ち受けポートは環境変数 PORT で変えられます。検証環境では、ビルドした .output(2.0MB)だけを別ディレクトリへコピーし、node_modulesが無い状態でもSSRとServer Routeが応答しました。このため、Dockerイメージにはビルド段階の依存を持ち込まず .output だけを入れれば足ります。

# Dockerfile
FROM node:24-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:24-slim
WORKDIR /app
ENV NODE_ENV=production PORT=3000
COPY --from=build /app/.output ./.output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

ビルド前に、プロジェクト直下の .dockerignore へ node_modules、.output、.git をそれぞれ別行で記載してください。ホスト側の依存や生成物が COPY . . で混入するのを防ぎます。このDockerfile自体は検証環境ではビルドしていません。根拠は前の段落の .output 単体での起動確認です。ベースイメージはenginesの22.12.0以上を満たすNode.js 24 LTSにしています。サーバーは全インターフェースで待ち受けるため、コンテナの外から -p 3000:3000 でつながります。

Bun:React 19が必要な専用手順

Hostingガイドは、Bun専用の手順はReact 19でしか動かず、React 18ならNode.jsの手順を使うよう明記しています。Viteでビルドする場合は nitro({ preset: 'bun' }) を指定します。もう1つの方法は公式サンプル start-bun の server.ts を使う構成で、bun run build のあと bun run server.ts で起動し、既定は3000番、5MB以下の静的ファイルをメモリに先読みします。BunとDeno・Node.jsの選び方はDeno・Bun比較の記事で扱っています。

セキュリティ勧告:2026年に出た3件と影響版

TanStack/routerリポジトリのセキュリティ勧告には、Startに関わるものが2026年に3件あります。本番運用では、利用中のパッケージとその依存先が勧告の対象版に含まれていないか確認してください。

勧告 公開日 内容 react-startの対象
CVE-2026-102989 2026-09-30 反射型XSS(CVSS 9.3) 1.143.12以上1.168.60未満
CVE-2026-45321 2026-05-11 npm公開経路の乗っ取り 1.167.68と1.167.71
GHSA-9m65-766c-r333 2026-05-08 別のサーバー関数を呼べる不具合 start-server-core 1.167.30未満

反射型XSSのCVE-2026-102989は、未認証の攻撃者がサーバー関数へのURLを細工し、開いた人のブラウザでアプリと同じオリジンのJavaScriptを実行させるものです。修正版は @tanstack/react-start 1.168.60(Solid版は1.168.57)です。報告者にはAIアプリ生成サービスのLovableが挙がっており、Lovableは新規アプリをTanStack Startで出力しています(Lovableの解説記事参照)。すぐ更新できない場合の暫定策として、勧告は /_serverFn/* への x-tsr-serverFn: true ヘッダーの無いリクエストやページ遷移(Sec-Fetch-Mode: navigate)をWAFで止める方法を挙げていますが、完全な対策ではないとも書いています。

2件目のCVE-2026-45321(CVSS 9.6)は、42個の @tanstack/* パッケージに認証情報を盗むコードが入った版が公開された事件です。利用しているロックファイルとCIのインストール履歴を確認してください。該当版をインストールした環境は侵害されたものとして扱い、インストール処理がアクセスできた認証情報を直ちにローテーションし、監査ログの調査とクリーンな環境への復旧を行ってください。

Next.jsとの選び分け:RSCの成熟度とURL状態の型推論

公式のTanStack Start vs Next.js(2026年9月11日にNext.js 16.3と照合と記載)は、Startを「Routerの型付きナビゲーション・検索パラメータ・loaderをフルスタックで使いたいとき」、Next.jsを「App RouterのServer Componentモデルと画像・フォント・メタデータの統合機能が欲しいとき」に向くと整理しています。性能の勝ち負けを示すベンチマークは無いとも明言しています。Next.js側の最小構成はNext.jsの16系App Routerの解説、Next.jsのままViteで動かす選択肢はVinextの解説を参照してください。

TanStack Startを選ぶ条件

検索条件やページ番号などの状態をURLに持ち、それを複数の画面で型付きで扱うアプリは、Startの利点が最も出ます。TanStack Queryをすでに使っているなら、公式の統合でloaderとクエリキャッシュをそろえられます(TanStack Queryの解説)。Server Componentsを使わなくてもSSRとサーバー関数が使える点も、クライアント主導で書きたいチームには合います。公式は、移行を決める前に代表的な1ルートで「実データの読み込み・検索パラメータの検証・更新処理・権限のないアクセス・デプロイ」まで試すよう勧めています。業務システムで同じ評価をしたい場合は業務用・Webアプリ開発の支援でも相談を受けています。

TanStack Startの採用を見送る条件

Server Componentsを前提にした設計ならNext.jsを選びます。StartのServer Componentsはドキュメント冒頭で「experimental」と明記されたオプトイン機能で、公式比較も「本番での機能同等として扱うべきではない」と書いています。next/image・next/font に依存した既存アプリも、置き換え自体が移行作業になります。正常に動いているNext.jsアプリを、ルーティングの書き方が好みだという理由だけで書き直すのは公式も勧めていません。

よくある質問

TanStack Startは本番運用できますか?

できます。2026年10月13日に1.0安定版が告知され、1.169が最初の安定版です。ただし反射型XSSの修正(1.168.60)より前の版は更新が必要で、React Server Componentsは実験的機能のままです。

Server FunctionsとServer Routes(API Routes)はどう使い分けますか?

同じStartアプリの画面やloaderから呼ぶ処理はServer Functions、Webhookや他のアプリから叩くAPIはServer Routesです。Server Functionsは同一オリジン向けのRPCで、既定のCSRF対策がクロスサイト要求を拒否します。ただしCSRF対策は認証・認可の代わりにはなりません。

TanStack StartはBunで動きますか?

動きます。Viteなら nitro({ preset: 'bun' })、または公式サンプルの server.ts を bun run server.ts で起動します。Bun専用の手順はReact 19が前提で、React 18ならNode.jsの手順を使います。

@tanstack/react-startと@tanstack/startはどう違いますか?

現行のパッケージは @tanstack/react-start(React)と @tanstack/solid-start(Solid)です。npmの @tanstack/start は latest が2025年6月8日公開の1.120.20のままなので新規採用は避け、Reactでは公式の導入手順が指定する @tanstack/react-start を使ってください。

開発サーバーのポートは何番ですか?

公式サンプルとBuild from Scratchは vite.config.ts で3000を指定しています。Vite自体の既定は5173なので、自分で設定を書いて server.port を省いた場合は5173で起動します。本番の node .output/server/index.mjs は環境変数 PORT で変えます。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.11.03 テックブログ ネットマイル不正アクセスで最大549万人:対象確認と退会会員データを消す実装
  2. 2026.11.06 テックブログ ドコモ不正アクセス:最大29.5万件の流出項目と見舞金、My docomo停止の経過と偽連絡の見分け方
  3. 2026.11.06 テックブログ あいおいニッセイ同和の不正アクセス:44.4万件の漏えい項目と窓口、止まった手続きの代わり
  4. 2026.11.07 コラム サイバーセキュリティ緊急対策パッケージとは?4本柱の施策と期限、企業と委託先が今やること
  5. 2026.11.06 テックブログ アイリスオーヤマ情報漏洩:アイリスプラザ770万人分の対象項目と本物の連絡の見分け方

RELATED POSTS 関連記事

目次