A professional portfolio and business website built with Next.js 15 and deployed on Cloudflare Workers.
- Framework: Next.js 15.5.7 (App Router)
- Deployment: Cloudflare Workers via OpenNext.js
- Database: Cloudflare D1 (SQLite)
- ORM: Drizzle ORM
- Styling: Tailwind CSS
- Language: TypeScript
- Validation: Zod
- Email: External Cloudflare Worker
- Contact Form - Database-backed with email notifications
- Tools Section - Utility tools (e.g., Reddit Post Date Extractor)
- Modular API Architecture - Clean, maintainable endpoint structure
- Type-Safe - Full TypeScript + Zod validation
- Edge-Optimized - Deployed on Cloudflare's global network
This project follows a clean, modular architecture pattern:
src/
βββ app/
β βββ api/ # API endpoints
β β βββ contact/ # Contact form API
β β β βββ route.ts # Request handler
β β β βββ validation.ts # Zod schemas
β β β βββ db.ts # Database operations
β β β βββ email-notification.ts
β β β βββ logger.ts
β β βββ reddit-post-date/ # Reddit tool API
β β βββ route.ts
β β βββ validation.ts
β β βββ reddit-client.ts
β βββ tools/ # Tool pages
β
βββ db/
β βββ client.ts # D1 database client
β βββ schema.ts # Drizzle schema
β
βββ lib/
βββ server/
βββ api-response.ts # Standardized responses
See claude.md for complete architecture documentation.
-
Clone the repository
git clone <repository-url> cd bobadilla-work
-
Install dependencies
npm install
-
Set up environment variables
cp .env.example .env # Edit .env with your configuration -
Initialize local D1 database
npx wrangler d1 execute bobadilla-work --local --file=./drizzle/migrations/0000_*.sql -
Start development server
npm run dev
Open http://localhost:3001 in your browser.
# Run Next.js dev server (with local D1)
npm run dev
# Preview on Cloudflare runtime
npm run preview
# Build for production
npm run build
# Deploy to Cloudflare
npm run deploy
# Lint code
npm run lint
# Type check
npm run type-checkThis project uses Cloudflare D1 (serverless SQLite) with Drizzle ORM for type-safe database operations.
Database: bobadilla-work Binding: DB
Generate migration:
# 1. Edit src/db/schema.ts
# 2. Generate migration
npx drizzle-kit generateApply migration:
# Local
npx wrangler d1 execute bobadilla-work --local --file=./drizzle/migrations/XXXX.sql
# Production
npx wrangler d1 execute bobadilla-work --remote --file=./drizzle/migrations/XXXX.sqlQuery database:
# Local
npx wrangler d1 execute bobadilla-work --local --command="SELECT * FROM contact_messages"
# Production
npx wrangler d1 execute bobadilla-work --remote --command="SELECT * FROM contact_messages"Visual database browser:
npx drizzle-kit studioConfigure in Cloudflare Dashboard or via wrangler:
EMAIL_WORKER_URL- External email worker endpointEMAIL_WORKER_API_KEY- Email worker authentication
D1 database binding is configured in wrangler.jsonc (no secrets needed).
This project follows specific architectural patterns for maintainability:
- Modular API Endpoints - Self-contained with separated concerns
- Standardized Responses - Consistent JSON format across all APIs
- Type Safety - Full TypeScript + Zod validation
- Clean Separation - Validation, business logic, and data access are separate
See claude.md for:
- API endpoint structure guidelines
- Coding conventions
- Creating new endpoints
- Best practices
# Test contact form locally
curl -X POST http://localhost:3001/api/contact \
-H "Content-Type: application/json" \
-d '{
"name": "Test User",
"email": "[email protected]",
"message": "This is a test message"
}'# Add test data
npx wrangler d1 execute bobadilla-work --local --command="
INSERT INTO contact_messages (name, email, company, message)
VALUES ('Test', '[email protected]', 'Test Co', 'Test message')
"
# Query test data
npx wrangler d1 execute bobadilla-work --local --command="
SELECT * FROM contact_messages ORDER BY created_at DESC LIMIT 5
"