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-start1.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 で変えます。