TanStack Start を久々に試す on Cloudflare

このスクラップについて
Cloudflare で React Router v7 を使うのが疲れてきたので TanStack Start だったらどんな感じなのかを試してみる。

まずは普通に TanStack Start を使う
cd ~/workspace
mkdir scrap-tanstack-start
cd scrap-tanstack-start
npm init -y
touch tsconfig.json
pnpm i @tanstack/react-start @tanstack/react-router vite
pnpm i react react-dom
pnpm i -D typescript @types/react @types/react-dom vite-tsconfig-paths
touch vite.config.ts
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ES2022",
"skipLibCheck": true,
"strictNullChecks": true
}
}
{
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build"
}
}
次回は Add the Basic Templating から始める。

続き
mkdir -p src/routes
touch src/router.tsx
touch src/routes/__root.tsx
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
export function createRouter() {
const router = createTanStackRouter({
routeTree,
scrollRestoration: true,
});
return router;
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}
}
import {
createRootRoute,
HeadContent,
Outlet,
Scripts,
} from "@tanstack/react-router";
import type { ReactNode } from "react";
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
{
title: "TanStack Start Starter",
},
],
}),
component: RootComponent,
});
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
);
}
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
);
}

最初のルート
touch src/routes/index.tsx
import * as fs from "node:fs";
import { createFileRoute, useRouter } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
const filePath = "count.txt";
async function readCount() {
return parseInt(
await fs.promises.readFile(filePath, "utf-8").catch(() => "0"),
);
}
const getCount = createServerFn({
method: "GET",
}).handler(() => {
return readCount();
});
const updateCount = createServerFn({
method: "POST",
})
.validator((d: number) => d)
.handler(async ({ data }) => {
const count = await readCount();
await fs.promises.writeFile(filePath, `${count + data}`);
});
export const Route = createFileRoute("/")({
component: Home,
loader: async () => await getCount(),
});
function Home() {
const router = useRouter();
const state = Route.useLoaderData();
return (
<button
type="button"
onClick={() => {
updateCount({ data: 1 }).then(() => {
router.invalidate();
});
}}
>
Add 1 to {state}?
</button>
);
}
Add 1 to 0?
ボタンが表示され、クリックすると Add 1 to 1?
ボタンに変化した。
また、count.txt ファイルが生成され、内容が 1 であることを確認した。

次にやること
スタートテンプレートでも眺めてみよう。

スタートテンプレート
GitPick についてはこちら。
中身を見てみるとスクラッチで作ったものよりもかなり複雑なようだ。

次回は TanStack Start on Cloudflare をやっていこう
cloudflareに乗せられましたか?

そらさん、コメントありがとうございます!
まだ乗せられていませんが、おかげさまでモチベーションが再燃したので再開しようと思います!
すみません変なコメントを残してしまい!、、
続きがすごく気になりますので、応援しています📣📣📣!

ありがとうございます!とても励みになります!
最近はちょっとバタバタしているのですが 1 日 15 分だけでも進めてみようと思います!

久々に再開してみる
そらさんからコメントをいただいたおかげでモチベーションが復活したので少しずつでも良いので進めていこう。
まずはリハビリに普通に TanStack Start を使うところから始めてみよう。

TanStack Start の Getting Started

コマンド
cd ~/workspace
pnpm dlx create-start-app@latest my-start-app
cd my-start-app
pnpm dev
http://localhost:3000/ にアクセスするとページが表示される。

Cloudflare Workers の乗っける準備
vite.config.ts を編集し、TanStack Start の Vite プラグインのビルドターゲットを cloudflare-module
に設定する。
export default defineConfig({
plugins: [
tanstackStart({
target: "cloudflare-module",
}),
],
});

次は Wrangler ファイルを追加する
その前に下記について軽く調べるところから始めよう、例えば下記は必須なのか?
- observability
- kv_namespaces
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-start-app",
"main": ".output/server/index.mjs",
"compatibility_date": "2025-09-04",
"compatibility_flags": ["nodejs_compat"],
"assets": {
"directory": ".output/public"
},
"observability": {
"enabled": true
},
"kv_namespaces": [
{
"binding": "CACHE",
"id": "<Your KV ID>"
}
]
}

使用する wrangler.jsonc
AI に聞いたところ両方とも必須ではないが、observability は有効にしておいても良さそうだ。
というわけで kv_namespaces を削除したものを使用しよう。
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-start-app",
"main": ".output/server/index.mjs",
"compatibility_date": "2025-09-04",
"compatibility_flags": ["nodejs_compat"],
"assets": {
"directory": ".output/public"
},
"observability": {
"enabled": true
}
}

ところで jsonc って何?
JSON with Comments のようだ、VS Code の下の方に書いてた。
ということは wrangler.json でも良いのだな。

package.json へのコマンド追加
{
"scripts": {
...
"deploy": "pnpm build && wrangler deploy",
"cf-typegen": "wrangler types --env-interface Env"
}
}

ビルドの実行
pnpm build
エラー発生。
[nitro 5:27:20] ERROR RollupError: node_modules/.pnpm/@tanstack+devtools@0.3.0_csstype@3.1.3_solid-js@1.9.9/node_modules/@tanstack/devtools/dist/esm/components/content-panel.js (1:19): "use" is not exported by "node_modules/.pnpm/solid-js@1.9.9/node_modules/solid-js/web/dist/server.js", imported by "node_modules/.pnpm/@tanstack+devtools@0.3.0_csstype@3.1.3_solid-js@1.9.9/node_modules/@tanstack/devtools/dist/esm/components/content-panel.js".
1: import { template, use, insert, memo, addEventListener, effect, className, delegateEvents } from "solid-js/web";
^
2: import { useDevtoolsSettings } from "../context/use-devtools-context.js";
3: import { useStyles } from "../styles/use-styles.js";

TanStack Devtools を無効にしてみた
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
// import { TanstackDevtools } from '@tanstack/react-devtools'
import Header from '../components/Header'
import appCss from '../styles.css?url'
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: 'utf-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
{
title: 'TanStack Start Starter',
},
],
links: [
{
rel: 'stylesheet',
href: appCss,
},
],
}),
shellComponent: RootDocument,
})
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
<Header />
{children}
{/* <TanstackDevtools
config={{
position: 'bottom-left',
}}
plugins={[
{
name: 'Tanstack Router',
render: <TanStackRouterDevtoolsPanel />,
},
]}
/> */}
<Scripts />
</body>
</html>
)
}
無事にビルドできたようだ。
vite v6.3.5 building for production...
Generated route tree in 99ms
✓ 135 modules transformed.
.tanstack/start/build/client-dist/.vite/manifest.json 1.75 kB │ gzip: 0.43 kB
.tanstack/start/build/client-dist/assets/logo-CHtJT8UQ.svg 8.61 kB │ gzip: 3.65 kB
.tanstack/start/build/client-dist/assets/styles-CL-3Q1mh.css 14.74 kB │ gzip: 3.56 kB
.tanstack/start/build/client-dist/assets/index-DcelEwDw.js 0.83 kB │ gzip: 0.47 kB
.tanstack/start/build/client-dist/assets/demo.start.api-request-wXd1-3Wl.js 0.89 kB │ gzip: 0.53 kB
.tanstack/start/build/client-dist/assets/demo.start.server-funcs-BwrR4NrQ.js 1.73 kB │ gzip: 0.93 kB
.tanstack/start/build/client-dist/assets/main-LwwufoTX.js 281.72 kB │ gzip: 89.17 kB
✓ built in 1.28s
vite v6.3.5 building SSR bundle for production...
✓ 113 modules transformed.
[plugin vite:css-post] Sourcemap is likely to be incorrect: a plugin (vite:css-post) was used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help
✓ built in 493ms
✔ Generated public .output/public nitro 5:33:26
[nitro 5:33:27] ℹ Building Nitro Server (preset: cloudflare-module, compatibility date: 2024-11-19)
✔ Nitro Server built nitro 5:33:30
├─ .output/server/chunks/_/_tanstack-start-manifest_v-DkvVKkKB.mjs (862 B) (348 B gzip)
├─ .output/server/chunks/_/_tanstack-start-manifest_v-DkvVKkKB.mjs.map (122 B) (120 B gzip)
├─ .output/server/chunks/_/_tanstack-start-server-fn-manifest_v-DXGikrzB.mjs (475 B) (251 B gzip)
├─ .output/server/chunks/_/_tanstack-start-server-fn-manifest_v-DXGikrzB.mjs.map (132 B) (127 B gzip)
├─ .output/server/chunks/_/demo.start.api-request-Ve_rBhMG.mjs (1.15 kB) (634 B gzip)
├─ .output/server/chunks/_/demo.start.api-request-Ve_rBhMG.mjs.map (797 B) (463 B gzip)
├─ .output/server/chunks/_/demo.start.server-funcs-BQ7ujM8Y.mjs (2.39 kB) (1.22 kB gzip)
├─ .output/server/chunks/_/demo.start.server-funcs-BQ7ujM8Y.mjs.map (2.04 kB) (1.04 kB gzip)
├─ .output/server/chunks/_/demo.start.server-funcs-C4TQEO_Z.mjs (2.71 kB) (1.29 kB gzip)
├─ .output/server/chunks/_/demo.start.server-funcs-C4TQEO_Z.mjs.map (2.33 kB) (1.14 kB gzip)
├─ .output/server/chunks/_/demo.start.server-funcs-D8396NO4.mjs (874 B) (497 B gzip)
├─ .output/server/chunks/_/demo.start.server-funcs-D8396NO4.mjs.map (976 B) (560 B gzip)
├─ .output/server/chunks/_/index-Dzbi00z0.mjs (1.08 kB) (571 B gzip)
├─ .output/server/chunks/_/index-Dzbi00z0.mjs.map (637 B) (369 B gzip)
├─ .output/server/chunks/_/ssr.mjs (374 kB) (107 kB gzip)
├─ .output/server/chunks/_/ssr.mjs.map (45.3 kB) (12.4 kB gzip)
├─ .output/server/chunks/nitro/nitro.mjs (81.9 kB) (27.2 kB gzip)
├─ .output/server/chunks/nitro/nitro.mjs.map (19.6 kB) (6.47 kB gzip)
└─ .output/server/index.mjs (252 B) (188 B gzip)
Σ Total size: 538 kB (162 kB gzip)
ℹ Generated .output/public/_headers 5:33:30
[nitro 5:33:30] ✔ You can preview this build using npx wrangler dev .output/server/index.mjs --assets .output/public
[nitro 5:33:30] ✔ You can deploy this build using npx wrangler deploy .output/server/index.mjs --assets .output/public
[nitro 5:33:30] ✔ Client and Server bundles for TanStack Start have been successfully built.

デプロイ
wrangler をインストールする。
npm i -g wrangler
pnpm run deploy
無事にデプロイできた。

次にやること
SSR できているかどうかを確かめるためにサーバー側から変数を渡してみよう。