From init-stack
Scaffold a new full-stack project with TanStack Start + Cloudflare Workers + shadcn/ui + Tailwind CSS v4 + Hugeicons + bun. Use this skill whenever the user says "init project", "bootstrap a new app", "set up the stack", "create a new project", "start a new app", or any variation of initializing a fresh codebase with this tech stack. Also trigger when the user asks to recreate the boilerplate, start from scratch, or set up a new repo with TanStack, Cloudflare Workers, or shadcn. Do not wait for the user to list every technology — if it sounds like they want a new project and this stack is in scope, use this skill.
How this skill is triggered — by the user, by Claude, or both
Slash command
/init-stack:init-stackThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Bootstrap a production-ready full-stack app: **TanStack Start** · **Cloudflare Workers** · **shadcn/ui** · **Tailwind CSS v4** · **Hugeicons** · **bun**
Bootstrap a production-ready full-stack app: TanStack Start · Cloudflare Workers · shadcn/ui · Tailwind CSS v4 · Hugeicons · bun
Project name/path (if provided): $ARGUMENTS
If $ARGUMENTS is empty, ask the user: "What's the project name and where should I create it?" Otherwise, use the provided name/path directly. The project name goes into wrangler.jsonc and package.json.
cd into the target directory before running any commands.
bun init -y
rm -f index.ts README.md # clean up bun's default files
# Runtime deps
bun add @tanstack/react-start @tanstack/react-router react react-dom tailwindcss @tailwindcss/vite
# Dev deps
bun add -D vite @vitejs/plugin-react @types/react @types/react-dom @cloudflare/vite-plugin wrangler typescript @biomejs/biome
bunx biome init
Then overwrite biome.json with the following (uses local $schema from node_modules):
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": ["**", "!**/src/routeTree.gen.ts"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
tsconfig.jsonWhy these settings matter:
moduleResolution: "Bundler"is required for Vite's import resolution to work correctly.verbatimModuleSyntaxmust be omitted — TanStack Start docs explicitly warn it causes server bundle leakage into client bundles.- The
@/*alias maps tosrc/*for clean imports throughout the project.
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"strictNullChecks": true,
"strict": true,
"noEmit": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src", "vite.config.ts"]
}
vite.config.tsPlugin order is critical.
cloudflare()must come beforetanstackStart(), andviteReact()must come aftertanstackStart(). Thecloudflareplugin targets the SSR environment so the Workers bundle is created correctly.
import { resolve } from "node:path";
import { cloudflare } from "@cloudflare/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
tailwindcss(),
cloudflare({ viteEnvironment: { name: "ssr" } }),
tanstackStart(),
// React plugin must come AFTER TanStack Start plugin
viteReact(),
],
resolve: {
alias: {
"@": resolve(__dirname, "./src"),
},
},
});
Why
resolve.aliasinstead ofvite-tsconfig-paths: The@cloudflare/vite-pluginsets up a custom SSR environment that can conflict withvite-tsconfig-paths. The explicit alias is simpler, has no extra dependency, and reliably resolves@/*imports in both client and Worker bundles.
wrangler.jsoncReplace <project-name> with the actual project name.
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "<project-name>",
"compatibility_date": "2025-09-02",
"compatibility_flags": ["nodejs_compat"],
"main": "@tanstack/react-start/server-entry",
"assets": {
"directory": ".output/static",
"binding": "ASSETS"
}
}
package.jsonReplace the bun init defaults with proper scripts. Keep all dependencies that were installed.
{
"name": "<project-name>",
"type": "module",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"deploy": "bun run build && wrangler deploy",
"typecheck": "tsc --noEmit",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"cf-typegen": "wrangler types"
}
}
Create the following files exactly as shown.
src/styles/globals.css@import "tailwindcss";
(shadcn init will expand this file with the full theme in Step 9)
src/router.tsximport { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function createRouter() {
const router = createTanStackRouter({
routeTree,
scrollRestoration: true,
})
return router
}
// Required by TanStack Start v1.166+ — the server calls getRouter() at runtime
export async function getRouter() {
return createRouter()
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
src/routes/__root.tsx/// <reference types="vite/client" />
import type { ReactNode } from 'react'
import '../styles/globals.css'
import {
Outlet,
createRootRoute,
HeadContent,
Scripts,
} from '@tanstack/react-router'
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: '<project-name>' },
],
links: [{ rel: 'icon', href: '/favicon.ico' }],
}),
component: RootComponent,
notFoundComponent: NotFoundComponent,
})
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
)
}
function NotFoundComponent() {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold">404</h1>
<p className="mt-2 text-muted-foreground">Page not found</p>
</div>
</div>
)
}
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}
src/routes/index.tsximport { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: Home,
})
function Home() {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold tracking-tight"><project-name></h1>
<p className="mt-2 text-muted-foreground">Ready to build.</p>
</div>
</div>
)
}
routeTree.gen.tsRun the dev server briefly — it will auto-generate src/routeTree.gen.ts on first start, then stop it.
# Start dev server, wait ~5 seconds, then Ctrl+C
bun run dev
Confirm src/routeTree.gen.ts now exists before proceeding.
bunx --bun shadcn@latest init --preset aLrO8A --base base --template start
This preset configures:
base-nova style with OKLCH colour system (light + dark)src/styles/globals.css@/* import aliases in components.jsonbun add @hugeicons/react @hugeicons/core-free-icons
Usage pattern going forward:
import { HugeiconsIcon } from '@hugeicons/react'
import { HomeIcon } from '@hugeicons/core-free-icons'
<HugeiconsIcon icon={HomeIcon} />
bun run typecheck
Must exit with zero errors. If there are errors, fix them before declaring the project ready.
Tell the user:
bun run dev → http://localhost:5173bunx --bun shadcn@latest add <component>wrangler.jsonc, then regenerate types with bun run cf-typegencreateServerFn from @tanstack/react-startbun run deployfs, path, process.env, etc. This is Cloudflare Workers only. Exception: node:path is fine in vite.config.ts (build-time only).globals.css under @theme inline.routeTree.gen.ts — it is auto-generated by the Vite plugin.vite.config.ts will break the build.verbatimModuleSyntax — never add this to tsconfig.json.getRouter is required — TanStack Start v1.166+ calls getRouter() at runtime on the router entry. Without it, every request crashes with entries.routerEntry.getRouter is not a function.@ alias must be in vite.config.ts — tsconfig.json paths alone aren't enough; Vite needs the explicit resolve.alias. Don't use vite-tsconfig-paths — it can conflict with the Cloudflare SSR environment.npx claudepluginhub fuongz/skills --plugin init-stackGenerates full-stack TanStack Start app on Cloudflare Workers: SSR, routing, D1+Drizzle DB, better-auth, Tailwind v4+shadcn/ui. Fresh file generation per project.
Scaffolds TanStack Start projects interactively with 30+ integrations like TanStack Query, Clerk auth, Drizzle ORM, Vercel deployment, and MCP server for AI agents.
Guides full-stack Next.js project scaffolding with Tailwind v4, shadcn/ui, better-auth, and Vercel deployment via six interactive steps and agent teams.