From 430fa41abdbd307cf5f7c407280def7a943a9792 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Sat, 2 Aug 2025 11:50:00 +0530 Subject: [PATCH] feat(cli): add nuxt + convex support (#458) --- .changeset/common-steaks-joke.md | 5 + apps/cli/src/constants.ts | 8 +- .../project-generation/post-installation.ts | 2 +- apps/cli/src/helpers/setup/api-setup.ts | 7 +- apps/cli/src/prompts/backend.ts | 11 +- apps/cli/src/prompts/frontend.ts | 2 +- apps/cli/src/validation.ts | 2 +- .../web}/nuxt/app/plugins/vue-query.ts.hbs | 0 .../convex/packages/backend/package.json.hbs | 4 +- .../todo/web/nuxt/app/pages/todos.vue | 108 ---------- .../todo/web/nuxt/app/pages/todos.vue.hbs | 195 ++++++++++++++++++ .../nuxt/app/{app.vue => app.vue.hbs} | 4 + .../nuxt/app/components/Header.vue.hbs | 1 - .../components/{Loader.vue => Loader.vue.hbs} | 0 .../{ModeToggle.vue => ModeToggle.vue.hbs} | 2 + .../frontend/nuxt/app/pages/index.vue.hbs | 41 +++- .../frontend/nuxt/nuxt.config.ts.hbs | 12 +- .../templates/frontend/nuxt/package.json.hbs | 2 - .../app/(home)/_components/stack-builder.tsx | 10 +- 19 files changed, 272 insertions(+), 144 deletions(-) create mode 100644 .changeset/common-steaks-joke.md rename apps/cli/templates/{frontend => api/orpc/web}/nuxt/app/plugins/vue-query.ts.hbs (100%) delete mode 100644 apps/cli/templates/examples/todo/web/nuxt/app/pages/todos.vue create mode 100644 apps/cli/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs rename apps/cli/templates/frontend/nuxt/app/{app.vue => app.vue.hbs} (78%) rename apps/cli/templates/frontend/nuxt/app/components/{Loader.vue => Loader.vue.hbs} (100%) rename apps/cli/templates/frontend/nuxt/app/components/{ModeToggle.vue => ModeToggle.vue.hbs} (93%) diff --git a/.changeset/common-steaks-joke.md b/.changeset/common-steaks-joke.md new file mode 100644 index 0000000..7ce4c51 --- /dev/null +++ b/.changeset/common-steaks-joke.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": minor +--- + +Add Nuxt + Convex support diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index 4c3dee6..cdd5511 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -101,11 +101,17 @@ export const dependencyVersionMap = { "@trpc/server": "^11.4.2", "@trpc/client": "^11.4.2", - convex: "^1.25.0", + convex: "^1.25.4", "@convex-dev/react-query": "^0.0.0-alpha.8", "convex-svelte": "^0.0.11", + "convex-nuxt": "0.1.5", + "convex-vue": "^0.1.5", "@tanstack/svelte-query": "^5.74.4", + + "@tanstack/vue-query-devtools": "^5.83.0", + "@tanstack/vue-query": "^5.83.0", + "@tanstack/react-query-devtools": "^5.80.5", "@tanstack/react-query": "^5.80.5", diff --git a/apps/cli/src/helpers/project-generation/post-installation.ts b/apps/cli/src/helpers/project-generation/post-installation.ts index 9b32855..45b8652 100644 --- a/apps/cli/src/helpers/project-generation/post-installation.ts +++ b/apps/cli/src/helpers/project-generation/post-installation.ts @@ -345,5 +345,5 @@ function getBunWebNativeWarning(): string { } function getWorkersDeployInstructions(runCmd?: string): string { - return `\n${pc.bold("Deploy frontend to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd || "bun run"} deploy`}`; + return `\n${pc.bold("Deploy frontend to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} run deploy`}`; } diff --git a/apps/cli/src/helpers/setup/api-setup.ts b/apps/cli/src/helpers/setup/api-setup.ts index b1c2021..6887adb 100644 --- a/apps/cli/src/helpers/setup/api-setup.ts +++ b/apps/cli/src/helpers/setup/api-setup.ts @@ -75,6 +75,8 @@ export async function setupApi(config: ProjectConfig) { if (api === "orpc") { await addPackageDependency({ dependencies: [ + "@tanstack/vue-query", + "@tanstack/vue-query-devtools", "@orpc/tanstack-query", "@orpc/client", "@orpc/server", @@ -219,7 +221,10 @@ export async function setupApi(config: ProjectConfig) { if (hasSvelteWeb) { webDepsToAdd.push("convex-svelte"); } - + if (hasNuxtWeb) { + webDepsToAdd.push("convex-nuxt"); + webDepsToAdd.push("convex-vue"); + } await addPackageDependency({ dependencies: webDepsToAdd, projectDir: webDir, diff --git a/apps/cli/src/prompts/backend.ts b/apps/cli/src/prompts/backend.ts index d9bf271..b5d47dc 100644 --- a/apps/cli/src/prompts/backend.ts +++ b/apps/cli/src/prompts/backend.ts @@ -9,9 +9,7 @@ export async function getBackendFrameworkChoice( ): Promise { if (backendFramework !== undefined) return backendFramework; - const hasIncompatibleFrontend = frontends?.some( - (f) => f === "nuxt" || f === "solid", - ); + const hasIncompatibleFrontend = frontends?.some((f) => f === "solid"); const backendOptions: Array<{ value: Backend; @@ -59,15 +57,10 @@ export async function getBackendFrameworkChoice( hint: "No backend server", }); - let initialValue = DEFAULT_CONFIG.backend; - if (hasIncompatibleFrontend && initialValue === "convex") { - initialValue = "hono"; - } - const response = await select({ message: "Select backend", options: backendOptions, - initialValue, + initialValue: DEFAULT_CONFIG.backend, }); if (isCancel(response)) { diff --git a/apps/cli/src/prompts/frontend.ts b/apps/cli/src/prompts/frontend.ts index 5b53235..e4eb9c0 100644 --- a/apps/cli/src/prompts/frontend.ts +++ b/apps/cli/src/prompts/frontend.ts @@ -75,7 +75,7 @@ export async function getFrontendChoice( const webOptions = allWebOptions.filter((option) => { if (backend === "convex") { - return option.value !== "nuxt" && option.value !== "solid"; + return option.value !== "solid"; } return true; }); diff --git a/apps/cli/src/validation.ts b/apps/cli/src/validation.ts index 4bf27f3..10db63b 100644 --- a/apps/cli/src/validation.ts +++ b/apps/cli/src/validation.ts @@ -205,7 +205,7 @@ export function processAndValidateFlags( if (providedFlags.has("frontend") && options.frontend) { const incompatibleFrontends = options.frontend.filter( - (f) => f === "nuxt" || f === "solid", + (f) => f === "solid", ); if (incompatibleFrontends.length > 0) { consola.fatal( diff --git a/apps/cli/templates/frontend/nuxt/app/plugins/vue-query.ts.hbs b/apps/cli/templates/api/orpc/web/nuxt/app/plugins/vue-query.ts.hbs similarity index 100% rename from apps/cli/templates/frontend/nuxt/app/plugins/vue-query.ts.hbs rename to apps/cli/templates/api/orpc/web/nuxt/app/plugins/vue-query.ts.hbs diff --git a/apps/cli/templates/backend/convex/packages/backend/package.json.hbs b/apps/cli/templates/backend/convex/packages/backend/package.json.hbs index b3184f5..4a46e81 100644 --- a/apps/cli/templates/backend/convex/packages/backend/package.json.hbs +++ b/apps/cli/templates/backend/convex/packages/backend/package.json.hbs @@ -9,9 +9,9 @@ "license": "ISC", "description": "", "devDependencies": { - "typescript": "^5.8.3" + "typescript": "^5.9.2" }, "dependencies": { - "convex": "^1.25.0" + "convex": "^1.25.4" } } diff --git a/apps/cli/templates/examples/todo/web/nuxt/app/pages/todos.vue b/apps/cli/templates/examples/todo/web/nuxt/app/pages/todos.vue deleted file mode 100644 index 5e3e26c..0000000 --- a/apps/cli/templates/examples/todo/web/nuxt/app/pages/todos.vue +++ /dev/null @@ -1,108 +0,0 @@ - - - diff --git a/apps/cli/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs b/apps/cli/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs new file mode 100644 index 0000000..0198d09 --- /dev/null +++ b/apps/cli/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs @@ -0,0 +1,195 @@ + + + diff --git a/apps/cli/templates/frontend/nuxt/app/app.vue b/apps/cli/templates/frontend/nuxt/app/app.vue.hbs similarity index 78% rename from apps/cli/templates/frontend/nuxt/app/app.vue rename to apps/cli/templates/frontend/nuxt/app/app.vue.hbs index e8b9138..33a03fd 100644 --- a/apps/cli/templates/frontend/nuxt/app/app.vue +++ b/apps/cli/templates/frontend/nuxt/app/app.vue.hbs @@ -1,5 +1,7 @@ diff --git a/apps/cli/templates/frontend/nuxt/app/components/Header.vue.hbs b/apps/cli/templates/frontend/nuxt/app/components/Header.vue.hbs index 2a554f3..c1e8daa 100644 --- a/apps/cli/templates/frontend/nuxt/app/components/Header.vue.hbs +++ b/apps/cli/templates/frontend/nuxt/app/components/Header.vue.hbs @@ -1,5 +1,4 @@ diff --git a/apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs b/apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs index 2dd8f1a..03447fb 100644 --- a/apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs +++ b/apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs @@ -2,12 +2,22 @@ export default defineNuxtConfig({ compatibilityDate: 'latest', devtools: { enabled: true }, - modules: ['@nuxt/ui'], + modules: [ + '@nuxt/ui' + {{#if (eq backend "convex")}}, + 'convex-nuxt' + {{/if}} + ], css: ['~/assets/css/main.css'], devServer: { port: 3001 }, ssr: false, + {{#if (eq backend "convex")}} + convex: { + url: process.env.NUXT_PUBLIC_CONVEX_URL, + }, + {{/if}} runtimeConfig: { public: { serverURL: process.env.NUXT_PUBLIC_SERVER_URL, diff --git a/apps/cli/templates/frontend/nuxt/package.json.hbs b/apps/cli/templates/frontend/nuxt/package.json.hbs index 676d580..fa34901 100644 --- a/apps/cli/templates/frontend/nuxt/package.json.hbs +++ b/apps/cli/templates/frontend/nuxt/package.json.hbs @@ -11,7 +11,6 @@ }, "dependencies": { "@nuxt/ui": "3.3.0", - "@tanstack/vue-query": "^5.83.0", "nuxt": "^4.0.2", "typescript": "^5.8.3", "vue": "^3.5.18", @@ -20,7 +19,6 @@ }, "devDependencies": { "tailwindcss": "^4.1.11", - "@tanstack/vue-query-devtools": "^5.83.0", "@iconify-json/lucide": "^1.2.57" } } diff --git a/apps/web/src/app/(home)/_components/stack-builder.tsx b/apps/web/src/app/(home)/_components/stack-builder.tsx index c420382..e01986c 100644 --- a/apps/web/src/app/(home)/_components/stack-builder.tsx +++ b/apps/web/src/app/(home)/_components/stack-builder.tsx @@ -233,7 +233,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => { }); } } - const incompatibleConvexFrontends = ["nuxt", "solid"]; + const incompatibleConvexFrontends = ["solid"]; const originalWebFrontendLength = nextStack.webFrontend.length; nextStack.webFrontend = nextStack.webFrontend.filter( (f) => !incompatibleConvexFrontends.includes(f), @@ -241,16 +241,14 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => { if (nextStack.webFrontend.length !== originalWebFrontendLength) { changed = true; notes.webFrontend.notes.push( - "Nuxt and Solid are not compatible with Convex backend and have been removed.", - ); - notes.backend.notes.push( - "Convex backend is not compatible with Nuxt or Solid.", + "Solid is not compatible with Convex backend and has been removed.", ); + notes.backend.notes.push("Convex backend is not compatible with Solid."); notes.webFrontend.hasIssue = true; notes.backend.hasIssue = true; changes.push({ category: "convex", - message: "Removed incompatible web frontends (Nuxt, Solid)", + message: "Removed incompatible web frontends (Solid)", }); } if (nextStack.nativeFrontend[0] === "none") {