Skip to content

Commit 0ae1347

Browse files
add workers support for tanstack start (#369)
1 parent 79479e0 commit 0ae1347

File tree

11 files changed

+252
-254
lines changed

11 files changed

+252
-254
lines changed

.changeset/upset-pears-argue.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-better-t-stack": minor
3+
---
4+
5+
add workers support for tanstack start

apps/cli/src/helpers/project-generation/add-deployment.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,6 @@ export async function addDeploymentToProject(
4242
);
4343
}
4444

45-
if (input.webDeploy === "workers") {
46-
const compatibleFrontends = [
47-
"tanstack-router",
48-
"react-router",
49-
"solid",
50-
"next",
51-
"svelte",
52-
];
53-
const hasCompatible = detectedConfig.frontend?.some((f) =>
54-
compatibleFrontends.includes(f),
55-
);
56-
if (!hasCompatible) {
57-
exitWithError(
58-
"Cloudflare Workers deployment requires a compatible web frontend (tanstack-router, react-router, solid, next, or svelte).",
59-
);
60-
}
61-
}
62-
6345
const config: ProjectConfig = {
6446
projectName: detectedConfig.projectName || path.basename(projectDir),
6547
projectDir,

apps/cli/src/helpers/project-generation/template-manager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,7 @@ export async function setupDeploymentTemplates(
851851

852852
const templateMap: Record<string, string> = {
853853
"tanstack-router": "react/tanstack-router",
854+
"tanstack-start": "react/tanstack-start",
854855
"react-router": "react/react-router",
855856
solid: "solid",
856857
next: "react/next",

apps/cli/src/helpers/setup/web-deploy-setup.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { PackageManager, ProjectConfig } from "../../types";
44
import { addPackageDependency } from "../../utils/add-package-deps";
55
import { setupNuxtWorkersDeploy } from "./workers-nuxt-setup";
66
import { setupSvelteWorkersDeploy } from "./workers-svelte-setup";
7+
import { setupTanstackStartWorkersDeploy } from "./workers-tanstack-start-setup";
78
import { setupWorkersVitePlugin } from "./workers-vite-setup";
89

910
export async function setupWebDeploy(config: ProjectConfig): Promise<void> {
@@ -18,6 +19,7 @@ export async function setupWebDeploy(config: ProjectConfig): Promise<void> {
1819
const isNuxt = frontend.includes("nuxt");
1920
const isSvelte = frontend.includes("svelte");
2021
const isTanstackRouter = frontend.includes("tanstack-router");
22+
const isTanstackStart = frontend.includes("tanstack-start");
2123
const isReactRouter = frontend.includes("react-router");
2224
const isSolid = frontend.includes("solid");
2325

@@ -27,6 +29,8 @@ export async function setupWebDeploy(config: ProjectConfig): Promise<void> {
2729
await setupNuxtWorkersDeploy(projectDir, packageManager);
2830
} else if (isSvelte) {
2931
await setupSvelteWorkersDeploy(projectDir, packageManager);
32+
} else if (isTanstackStart) {
33+
await setupTanstackStartWorkersDeploy(projectDir, packageManager);
3034
} else if (isTanstackRouter || isReactRouter || isSolid) {
3135
await setupWorkersWebDeploy(projectDir, packageManager);
3236
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import path from "node:path";
2+
import fs from "fs-extra";
3+
import {
4+
type CallExpression,
5+
Node,
6+
type ObjectLiteralExpression,
7+
SyntaxKind,
8+
} from "ts-morph";
9+
import type { PackageManager } from "../../types";
10+
import { addPackageDependency } from "../../utils/add-package-deps";
11+
import { ensureArrayProperty, tsProject } from "../../utils/ts-morph";
12+
13+
export async function setupTanstackStartWorkersDeploy(
14+
projectDir: string,
15+
packageManager: PackageManager,
16+
): Promise<void> {
17+
const webAppDir = path.join(projectDir, "apps/web");
18+
if (!(await fs.pathExists(webAppDir))) return;
19+
20+
await addPackageDependency({
21+
devDependencies: ["wrangler"],
22+
projectDir: webAppDir,
23+
});
24+
25+
const pkgPath = path.join(webAppDir, "package.json");
26+
if (await fs.pathExists(pkgPath)) {
27+
const pkg = await fs.readJson(pkgPath);
28+
pkg.scripts = {
29+
...pkg.scripts,
30+
deploy: `${packageManager} run build && wrangler deploy`,
31+
"cf-typegen": "wrangler types --env-interface Env",
32+
};
33+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
34+
}
35+
36+
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
37+
if (!(await fs.pathExists(viteConfigPath))) return;
38+
39+
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
40+
if (!sourceFile) return;
41+
42+
const defineCall = sourceFile
43+
.getDescendantsOfKind(SyntaxKind.CallExpression)
44+
.find((expr) => {
45+
const expression = expr.getExpression();
46+
return (
47+
Node.isIdentifier(expression) && expression.getText() === "defineConfig"
48+
);
49+
}) as CallExpression | undefined;
50+
51+
if (!defineCall) return;
52+
53+
const configObj = defineCall.getArguments()[0] as
54+
| ObjectLiteralExpression
55+
| undefined;
56+
if (!configObj) return;
57+
58+
const pluginsArray = ensureArrayProperty(configObj, "plugins");
59+
60+
const tanstackPluginIndex = pluginsArray
61+
.getElements()
62+
.findIndex((el) => el.getText().includes("tanstackStart("));
63+
64+
const tanstackPluginText = 'tanstackStart({ target: "cloudflare-module" })';
65+
66+
if (tanstackPluginIndex === -1) {
67+
pluginsArray.addElement(tanstackPluginText);
68+
} else {
69+
pluginsArray
70+
.getElements()
71+
[tanstackPluginIndex].replaceWithText(tanstackPluginText);
72+
}
73+
74+
await tsProject.save();
75+
}

apps/cli/src/prompts/web-deploy.ts

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@ import pc from "picocolors";
33
import { DEFAULT_CONFIG } from "../constants";
44
import type { Backend, Frontend, Runtime, WebDeploy } from "../types";
55

6-
const WORKERS_COMPATIBLE_FRONTENDS: Frontend[] = [
7-
"tanstack-router",
8-
"react-router",
9-
"solid",
10-
"next",
11-
"nuxt",
12-
"svelte",
13-
];
14-
156
type DeploymentOption = {
167
value: WebDeploy;
178
label: string;
@@ -38,18 +29,10 @@ export async function getDeploymentChoice(
3829
deployment?: WebDeploy,
3930
_runtime?: Runtime,
4031
_backend?: Backend,
41-
frontend: Frontend[] = [],
32+
_frontend: Frontend[] = [],
4233
): Promise<WebDeploy> {
4334
if (deployment !== undefined) return deployment;
4435

45-
const hasCompatibleFrontend = frontend.some((f) =>
46-
WORKERS_COMPATIBLE_FRONTENDS.includes(f),
47-
);
48-
49-
if (!hasCompatibleFrontend) {
50-
return "none";
51-
}
52-
5336
const options: DeploymentOption[] = [
5437
{
5538
value: "workers",
@@ -74,15 +57,12 @@ export async function getDeploymentChoice(
7457
}
7558

7659
export async function getDeploymentToAdd(
77-
frontend: Frontend[],
60+
_frontend: Frontend[],
7861
existingDeployment?: WebDeploy,
7962
): Promise<WebDeploy> {
8063
const options: DeploymentOption[] = [];
8164

82-
if (
83-
frontend.some((f) => WORKERS_COMPATIBLE_FRONTENDS.includes(f)) &&
84-
existingDeployment !== "workers"
85-
) {
65+
if (existingDeployment !== "workers") {
8666
const { label, hint } = getDeploymentDisplay("workers");
8767
options.push({
8868
value: "workers",

apps/cli/src/validation.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -451,24 +451,6 @@ export function processAndValidateFlags(
451451
process.exit(1);
452452
}
453453

454-
if (
455-
config.webDeploy === "workers" &&
456-
config.frontend &&
457-
config.frontend.length > 0
458-
) {
459-
const incompatibleFrontends = config.frontend.filter(
460-
(f) => f === "tanstack-start",
461-
);
462-
if (incompatibleFrontends.length > 0) {
463-
consola.fatal(
464-
`The following frontends are not compatible with '--web-deploy workers': ${incompatibleFrontends.join(
465-
", ",
466-
)}. Please choose a different frontend or remove '--web-deploy workers'.`,
467-
);
468-
process.exit(1);
469-
}
470-
}
471-
472454
return config;
473455
}
474456

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "../../node_modules/wrangler/config-schema.json",
3+
"name": "{{projectName}}",
4+
"main": ".output/server/index.mjs",
5+
"compatibility_date": "2025-07-05",
6+
"compatibility_flags": ["nodejs_compat"],
7+
"assets": {
8+
"directory": ".output/public",
9+
},
10+
"observability": {
11+
"enabled": true,
12+
},
13+
// "kv_namespaces": [
14+
// {
15+
// "binding": "CACHE",
16+
// "id": "<Your KV ID>",
17+
// },
18+
// ],
19+
}
20+

apps/cli/templates/frontend/react/web-base/_gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
.vinxi
2020
.output
2121
.react-router/
22+
.tanstack/
23+
.nitro/
2224

2325
# Deployment
2426
.vercel

0 commit comments

Comments
 (0)