Loading...
Loading...
Production-ready starter project for React + Cloudflare Workers + Hono with core services (D1, KV, R2, Workers AI) and optional advanced features (Clerk Auth, AI Chat, Queues, Vectorize). Complete with planning docs, session handoff protocol, and enable scripts for opt-in features. Use when: starting new full-stack project, creating Cloudflare app, scaffolding web app, AI-powered application, chat interface, RAG application, need complete starter, avoid setup time, production-ready template, full-stack boilerplate, React Cloudflare starter. Prevents: service configuration errors, binding setup mistakes, frontend-backend connection issues, CORS errors, auth integration problems, AI SDK setup confusion, missing planning docs, incomplete project structure, hours of initial setup. Keywords: cloudflare scaffold, full-stack starter, react cloudflare, hono template, production boilerplate, AI SDK integration, workers AI, complete starter project, D1 KV R2 setup, web app template, chat application scaffold, RAG starter, planning docs included, session handoff, tailwind v4 shadcn, typescript starter, vite cloudflare plugin, all services configured
npx skill4agent add jackspace/claudeskillz cloudflare-full-stack-scaffold# Copy the scaffold
cp -r scaffold/ my-new-app/
cd my-new-app/
# Install dependencies
npm install
# Initialize core services (D1, KV, R2)
./scripts/init-services.sh
# Create database tables
npm run d1:local
# Start developing
npm run devnpm run enable-authnpm run enable-ai-chatnpm run enable-queuesnpm run enable-vectorizescaffold/
├── package.json # All dependencies (React, Hono, AI SDK, Clerk)
├── tsconfig.json # TypeScript config
├── vite.config.ts # Cloudflare Vite plugin
├── wrangler.jsonc # All Cloudflare services configured
├── .dev.vars.example # Environment variables template
├── .gitignore # Standard ignores
├── README.md # Project-specific readme
├── CLAUDE.md # Project instructions for Claude
├── SCRATCHPAD.md # Session handoff protocol
├── CHANGELOG.md # Version history
├── schema.sql # D1 database schema
│
├── docs/ # Complete planning docs
│ ├── ARCHITECTURE.md
│ ├── DATABASE_SCHEMA.md
│ ├── API_ENDPOINTS.md
│ ├── IMPLEMENTATION_PHASES.md
│ ├── UI_COMPONENTS.md
│ └── TESTING.md
│
├── migrations/ # D1 migrations
│ └── 0001_initial.sql
│
├── src/ # Frontend (React + Vite + Tailwind v4)
│ ├── main.tsx
│ ├── App.tsx
│ ├── index.css # Tailwind v4 theming
│ ├── components/
│ │ ├── ui/ # shadcn/ui components
│ │ ├── ThemeProvider.tsx
│ │ ├── ProtectedRoute.tsx # Auth (COMMENTED)
│ │ └── ChatInterface.tsx # AI chat (COMMENTED)
│ ├── lib/
│ │ ├── utils.ts # cn() utility
│ │ └── api-client.ts # Fetch wrapper
│ └── pages/
│ ├── Home.tsx
│ ├── Dashboard.tsx
│ └── Chat.tsx # AI chat page (COMMENTED)
│
└── backend/ # Backend (Hono + Cloudflare)
├── src/
│ └── index.ts # Main Worker entry
├── middleware/
│ ├── cors.ts
│ └── auth.ts # JWT (COMMENTED)
├── routes/
│ ├── api.ts # Basic API routes
│ ├── d1.ts # D1 examples
│ ├── kv.ts # KV examples
│ ├── r2.ts # R2 examples
│ ├── ai.ts # Workers AI (direct binding)
│ ├── ai-sdk.ts # AI SDK examples (multiple providers)
│ ├── vectorize.ts # Vectorize examples
│ └── queues.ts # Queues examples
└── db/
└── queries.ts # D1 typed query helpersscripts/setup-project.shscripts/init-services.shscripts/enable-auth.shscripts/enable-ai-chat.shscripts/enable-queues.shscripts/enable-vectorize.shreferences/quick-start-guide.mdreferences/service-configuration.mdreferences/ai-sdk-guide.mdreferences/customization-guide.mdreferences/enabling-auth.md// Already works, no API key needed
const result = await c.env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [{ role: 'user', content: 'Hello' }]
})import { streamText } from 'ai'
import { createWorkersAI } from 'workers-ai-provider'
const workersai = createWorkersAI({ binding: c.env.AI })
const result = await streamText({
model: workersai('@cf/meta/llama-3-8b-instruct'),
messages: [{ role: 'user', content: 'Hello' }]
})import { openai } from '@ai-sdk/openai'
import { anthropic } from '@ai-sdk/anthropic'
// Switch providers in 1 line
const result = await streamText({
model: openai('gpt-4o'), // or anthropic('claude-sonnet-4-5')
messages: [{ role: 'user', content: 'Hello' }]
})import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { useState } from 'react'
function ChatInterface() {
const [input, setInput] = useState('')
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({
api: '/api/ai-sdk/chat',
}),
})
// Send message on Enter key
const handleKeyDown = (e) => {
if (e.key === 'Enter' && status === 'ready' && input.trim()) {
sendMessage({ text: input })
setInput('')
}
}
// Render messages (v5 uses message.parts[])
return (
<div>
{messages.map(m => (
<div key={m.id}>
{m.parts.map(part => {
if (part.type === 'text') return <div>{part.text}</div>
})}
</div>
))}
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={handleKeyDown}
disabled={status !== 'ready'}
/>
</div>
)
}import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
const form = useForm({
resolver: zodResolver(userSchema), // Zod validation
})
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}// Define schema once
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
})
// Infer TypeScript type
type User = z.infer<typeof userSchema>
// Use in frontend (React Hook Form)
resolver: zodResolver(userSchema)
// Use in backend (same schema!)
const validated = userSchema.parse(requestBody)// Fetch data with automatic caching
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => apiClient.get('/api/users'),
})
// Update data with mutations
const mutation = useMutation({
mutationFn: (newUser) => apiClient.post('/api/users', newUser),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] })
},
})shared/schemas//profile/dashboardsrc/components/UserProfileForm.tsxbackend/routes/forms.tsshared/schemas/userSchema.tsreferences/supporting-libraries-guide.md./scripts/enable-auth.sh
# Prompts for Clerk keys, uncomments all patterns# 1. Copy scaffold
cd /path/to/skills/cloudflare-full-stack-scaffold
cp -r scaffold/ ~/projects/my-new-app/
cd ~/projects/my-new-app/
# 2. Run setup
npm install
# 3. Initialize Cloudflare services
npx wrangler d1 create my-app-db
npx wrangler kv:namespace create my-app-kv
npx wrangler r2 bucket create my-app-bucket
npx wrangler vectorize create my-app-index --dimensions=1536
npx wrangler queues create my-app-queue
# 4. Update wrangler.jsonc with IDs from step 3
# 5. Create D1 tables
npx wrangler d1 execute my-app-db --local --file=schema.sql
# 6. Start dev server
npm run dev./scripts/enable-auth.sh
# Prompts for Clerk publishable and secret keys
# Uncomments all auth patterns
# Updates .dev.vars
npm run dev./scripts/enable-ai-chat.sh
# Uncomments ChatInterface component
# Uncomments Chat page
# Prompts for OpenAI/Anthropic API keys (optional)
npm run dev# Build
npm run build
# Deploy
npx wrangler deploy
# Migrate production database
npx wrangler d1 execute my-app-db --remote --file=schema.sql
# Set production secrets
npx wrangler secret put CLERK_SECRET_KEY
npx wrangler secret put OPENAI_API_KEYbackend/routes/vectorize.tswrangler.jsoncvite.config.tsbackend/src/index.ts// backend/routes/my-feature.ts
import { Hono } from 'hono'
export const myFeatureRoutes = new Hono()
myFeatureRoutes.get('/hello', (c) => {
return c.json({ message: 'Hello from my feature!' })
})
// backend/src/index.ts
import { myFeatureRoutes } from './routes/my-feature'
app.route('/api/my-feature', myFeatureRoutes)// Change this line:
model: openai('gpt-4o'),
// To this:
model: anthropic('claude-sonnet-4-5'),
// Or this:
model: google('gemini-2.5-flash'),
// Or use Workers AI:
const workersai = createWorkersAI({ binding: c.env.AI })
model: workersai('@cf/meta/llama-3-8b-instruct'),src/index.css:root {
--background: hsl(0 0% 100%); /* Change colors here */
--foreground: hsl(0 0% 3.9%);
--primary: hsl(220 90% 56%);
/* etc */
}// ✅ CORRECT: Use relative URLs
fetch('/api/data')
// ❌ WRONG: Don't use absolute URLs
fetch('http://localhost:8787/api/data')VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxCLERK_SECRET_KEY=sk_test_xxx
OPENAI_API_KEY=sk-xxx// ✅ CORRECT ORDER
app.use('/api/*', corsMiddleware)
app.post('/api/data', handler)
// ❌ WRONG - Will cause CORS errors
app.post('/api/data', handler)
app.use('/api/*', corsMiddleware)isLoadedconst { isLoaded, isSignedIn } = useSession()
useEffect(() => {
if (!isLoaded) return // Wait for auth
fetch('/api/protected').then(/* ... */)
}, [isLoaded])import { jwtAuthMiddleware } from './middleware/auth'
app.use('/api/protected/*', jwtAuthMiddleware){
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0",
"hono": "^4.10.2",
"@cloudflare/vite-plugin": "^1.13.14",
"ai": "^5.0.76",
"@ai-sdk/openai": "^1.0.0",
"@ai-sdk/anthropic": "^1.0.0",
"@ai-sdk/google": "^1.0.0",
"workers-ai-provider": "^2.0.0",
"@ai-sdk/react": "^1.0.0",
"@clerk/clerk-react": "^5.53.3",
"@clerk/backend": "^2.19.0",
"tailwindcss": "^4.1.14",
"@tailwindcss/vite": "^4.1.14",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"tailwind-merge": "^2.5.4",
"zod": "^3.24.1",
"react-hook-form": "^7.54.2",
"@hookform/resolvers": "^3.9.1",
"@tanstack/react-query": "^5.62.11",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.4"
},
"devDependencies": {
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"typescript": "^5.7.2",
"vite": "^7.1.11",
"wrangler": "^4.0.0"
}
}| Scenario | Without Scaffold | With Scaffold | Savings |
|---|---|---|---|
| Initial setup | ~18-22k tokens | ~3-5k tokens | ~80% |
| Service configuration | ~8-10k tokens | 0 tokens (done) | 100% |
| Frontend-backend connection | ~5-7k tokens | 0 tokens (done) | 100% |
| AI SDK setup | ~4-6k tokens | 0 tokens (done) | 100% |
| Auth integration | ~6-8k tokens | ~500 tokens | ~90% |
| Planning docs | ~3-5k tokens | 0 tokens (included) | 100% |
| Total | ~44-58k tokens | ~3-6k tokens | ~90% |
| Issue | How Scaffold Prevents It |
|---|---|
| Service binding errors | All bindings pre-configured and tested |
| CORS errors | Middleware in correct order |
| Auth race conditions | Proper loading state patterns |
| Frontend-backend connection | Vite plugin correctly configured |
| AI SDK setup confusion | Multiple working examples |
| Missing planning docs | Complete docs/ structure included |
| Environment variable mix-ups | Clear .dev.vars.example with comments |
| Missing migrations | migrations/ directory with examples |
| Inconsistent file structure | Standard, tested structure |
| Database type errors | Typed query helpers included |
| Theme configuration | Tailwind v4 theming pre-configured |
| Build errors | Working build config (vite + wrangler) |
cp -r scaffold/ my-app/
cd my-app/
npm install
# Follow quick-start-guide.md./scripts/enable-auth.sh./scripts/enable-ai-chat.shnpm run build
npx wrangler deploywrangler.jsoncvite.config.ts.dev.vars.exampledocs/ARCHITECTURE.mdSCRATCHPAD.md