Development Guide

February 27, 2026 ยท View on GitHub

This guide helps you quickly master the projectโ€™s development workflow and best practices.

๐Ÿ—๏ธ Project Architecture

Tech Stack

  • Framework: Next.js 15.5.2 + App Router
  • Language: TypeScript (strict mode)
  • Styles: Tailwind CSS
  • Runtime: Cloudflare Workers Edge Runtime
  • Database: Cloudflare D1 (SQLite)
  • Storage: Cloudflare R2
  • Cache: Cloudflare KV
  • Testing: Vitest
  • Package Manager: pnpm

Directory Structure

cloudflare-worker-template/
โ”œโ”€โ”€ app/                 # Next.js app directory
โ”‚   โ”œโ”€โ”€ api/            # API routes (Edge Runtime)
โ”‚   โ”œโ”€โ”€ layout.tsx      # Root layout
โ”‚   โ””โ”€โ”€ page.tsx        # Home page
โ”œโ”€โ”€ lib/                # Libraries
โ”‚   โ”œโ”€โ”€ db/            # D1 DB wrapper
โ”‚   โ”œโ”€โ”€ r2/            # R2 wrapper
โ”‚   โ””โ”€โ”€ cache/         # KV cache wrapper
โ”œโ”€โ”€ components/         # React components
โ”œโ”€โ”€ types/             # TypeScript types
โ”œโ”€โ”€ migrations/        # DB migrations
โ”œโ”€โ”€ scripts/           # Automation scripts
โ””โ”€โ”€ __tests__/         # Tests

๐Ÿš€ Development Workflow

1. Local Development

# Local development (runs in Workers runtime with Cloudflare bindings)
pnpm dev

2. Code Quality

The project enforces code quality:

# Format code
pnpm format

# Check formatting
pnpm run format:check

# ESLint
pnpm lint

# TypeScript typeโ€‘check
pnpm run type-check

3. Testโ€‘Driven Development

# Run all tests
pnpm test

# Watch mode (recommended during dev)
pnpm run test:watch

# Coverage
pnpm run test:coverage

๐Ÿ“ Writing Code

API Route Example

Create new API routes under app/api/:

// app/api/example/route.ts
import { NextRequest } from 'next/server';
import { withRepositories, successResponse } from '@/lib/api';

export const runtime = 'edge'; // Edge Runtime friendly

export async function GET(request: NextRequest) {
  return withRepositories(request, async repos => {
    // ไป“ๅ‚จๅฑ‚่ดŸ่ดฃๆ•ฐๆฎๅบ“่ฎฟ้—ฎ๏ผŒAPI ๅชๅ…ณๆณจไธšๅŠก้€ป่พ‘
    const users = await repos.users.findAll();
    return successResponse(users, 'Users retrieved successfully');
  });
}

Database Operations

import { NextRequest } from 'next/server';
import { withRepositories, successResponse } from '@/lib/api';
import { withCache } from '@/lib/cache/client';

export async function GET(request: NextRequest) {
  return withRepositories(request, async repos => {
    const users = await repos.users.findAll('asc');

    const posts = await withCache(
      'posts:latest',
      () => repos.posts.findAll({ take: 5, published: true }),
      300
    );

    return successResponse({ users, posts });
  });
}

R2 Storage Operations

import { createR2Client } from '@/lib/r2/client';

const r2 = createR2Client();

// Upload file
await r2.put('uploads/file.jpg', fileData, {
  httpMetadata: { contentType: 'image/jpeg' },
});

// Download file
const object = await r2.get('uploads/file.jpg');
const blob = await object.blob();

// Delete file
await r2.delete('uploads/file.jpg');

// List files
const list = await r2.list({ prefix: 'uploads/' });

KV Cache Operations

import { withCache } from '@/lib/cache/client';

// Use cache wrapper
const data = await withCache(
  'cache-key',
  async () => {
    // Expensive operation
    return await fetchExpensiveData();
  },
  3600 // TTL (seconds)
);

๐Ÿ—„๏ธ Database Management

The project uses Wrangler D1 migrations to manage schema, and Prisma for typeโ€‘safe queries. In daily dev:

  • Use pnpm run db:migrations:create to generate a migration
  • Run pnpm run db:migrate:local / pnpm run db:migrate:test / pnpm run db:migrate:prod
  • After schema changes, update prisma/schema.prisma and run pnpm prisma:generate

Naming conventions, rollback strategy, and data migration scripting are detailed in Migrations Guide.

๐Ÿงช Writing Tests

Unit Test Example

// __tests__/lib/my-feature.test.ts
import { describe, it, expect, vi } from 'vitest';
import { myFunction } from '@/lib/my-feature';

describe('My Feature', () => {
  it('should work correctly', () => {
    const result = myFunction('input');
    expect(result).toBe('expected output');
  });

  it('should handle errors', () => {
    expect(() => myFunction(null)).toThrow();
  });
});

Mock Cloudflare Bindings

const mockDB = {
  prepare: vi.fn().mockReturnThis(),
  bind: vi.fn().mockReturnThis(),
  all: vi.fn().mockResolvedValue({ results: [] }),
} as any;

๐Ÿ”„ Git Workflow

Branching Strategy

  • main โ€” production (auto deploy)
  • develop โ€” test (auto deploy)
  • feature/* โ€” feature development
  • fix/* โ€” bug fixes

Commit Conventions

Use Conventional Commits:

feat: add user authentication
fix: fix file upload issue
docs: update API docs
test: add user tests
refactor: refactor DB queries
style: format code
chore: update dependencies

Release Management

The project uses automated CHANGELOG generation via release-please. To trigger a release:

Include [release] in your commit message:

# Regular commits (not included in immediate CHANGELOG)
git commit -m "feat: add new feature"
git commit -m "fix: resolve bug"

# Release commit (triggers CHANGELOG PR creation)
git commit -m "chore: prepare v1.2.0 release [release]"

What happens when you use [release]:

  1. โœ… GitHub Actions detects the [release] tag
  2. โœ… release-please scans all commits since the last release
  3. โœ… Automatically creates a PR with:
    • Updated CHANGELOG.md (all feat/fix/docs commits)
    • Version bump in package.json (following semver)
  4. โœ… Merge the PR to publish the release

Best practices:

  • Use [release] only when you're ready to cut a new version
  • All feature/fix commits between releases will be included automatically
  • No need to manually update CHANGELOG or version numbers

Development Flow

# 1. Create a feature branch
git checkout -b feature/awesome-feature

# 2. Develop and test
pnpm dev
pnpm test

# 3. Commit code
git add .
git commit -m "feat: add awesome feature"

# 4. Push to remote
git push origin feature/awesome-feature

# 5. Open a Pull Request
# CI runs tests automatically

โš™๏ธ GitHub Actions Setup

Enable Workflow Permissions

To allow GitHub Actions to automatically create pull requests (e.g., for CHANGELOG updates via release-please), configure repository settings:

  1. Navigate to repository settings:

    • Go to your repository: https://github.com/YOUR_USERNAME/YOUR_REPO
    • Click Settings in the top menu
  2. Configure Actions permissions:

    • In the left sidebar, click Actions โ†’ General
    • Or directly visit: https://github.com/YOUR_USERNAME/YOUR_REPO/settings/actions
  3. Update Workflow permissions:

    Scroll to the "Workflow permissions" section at the bottom:

    โ—‹ Read repository contents and packages permissions
    โ— Read and write permissions  โ† Select this
    
    โ˜‘ Allow GitHub Actions to create and approve pull requests  โ† Check this
    
  4. Save settings:

    • Click Save to apply changes

Why This Is Required

By default, GitHub restricts Actions from creating pull requests for security. Our automated workflows need these permissions to:

  • Auto-update CHANGELOG: The release-please workflow creates PRs with version updates
  • Dependency updates: Automated dependency update bots (e.g., Dependabot, Renovate)
  • Code generation: Workflows that auto-generate code and create PRs

๐Ÿ”ง Troubleshooting

View logs

# View Cloudflare Workers logs in real-time
wrangler tail

Local DB queries

wrangler d1 execute cloudflare-worker-template-local --local \
  --command="SELECT * FROM users"

Common issues

pnpm install failed?

pnpm store prune && pnpm install

Type errors?

pnpm run type-check

Tests failing? Check Cloudflare bindings are configured correctly.

Local DB is empty?

pnpm run db:migrate:local
pnpm run db:seed -- --env=local

๐Ÿ“– Further Reading