CLAUDE.md Claude Code templates examples AI coding 2026 copy-paste

10 CLAUDE.md Template Examples That Actually Work (2026)

The Prompt Shelf ·

The existing CLAUDE.md templates by use case cover 10 common project types. This guide goes further: each template here is annotated with why each section exists and what specific Claude behavior it prevents or enables. These are not minimal examples — they are production-ready starting points calibrated for real-world complexity.

Template 1: React SPA with Vite + TypeScript

What Claude gets wrong without this: Mixing Vite and CRA assumptions, using outdated hook patterns, ignoring the existing component architecture, adding unnecessary dependencies.

# Project

Customer-facing dashboard SPA. React 19, TypeScript strict mode, Vite 6.
State management: Zustand. Data fetching: TanStack Query v5.

## Commands

```bash
pnpm dev               # Vite dev server on :5173
pnpm build             # TypeScript check + Vite build
pnpm test              # Vitest unit tests
pnpm test:ui           # Vitest UI (browser-based test runner)
pnpm test:e2e          # Playwright E2E tests
pnpm lint              # ESLint
pnpm typecheck         # tsc --noEmit

Project Structure

src/
  features/         # feature-sliced architecture
    auth/
      components/   # feature-specific components
      hooks/        # feature-specific hooks
      store.ts      # Zustand slice
      api.ts        # TanStack Query hooks
    dashboard/
    users/
  shared/
    components/     # shared UI primitives
    hooks/          # shared hooks
    lib/            # utilities, formatters
  app/
    router.tsx      # React Router config
    providers.tsx   # Root providers

Anti-Patterns

  • No prop drilling — use Zustand for cross-component state
  • No useEffect for data fetching — use TanStack Query
  • No direct API calls in components — all API calls go through features/*/api.ts
  • No any types — use unknown and narrow, or define proper types
  • No barrel exports (index.ts re-exporting everything) in shared/ — import directly
  • No class components
  • No React.FC type — use plain function declarations

State Management Rules

Zustand slices: one file per feature, exported as a named hook.

// Good
export const useAuthStore = create<AuthState>()((set) => ({
  user: null,
  setUser: (user) => set({ user }),
}))

// Never use zustand for server state — that's TanStack Query's job

Testing

Vitest + React Testing Library. Test behavior, not implementation.

# Run tests for a specific feature
pnpm test src/features/auth
# Run with coverage
pnpm test --coverage

Mock API calls at the network level with MSW — do not mock module imports for API functions.

Before Done

  • pnpm typecheck passes (no new TS errors)
  • pnpm lint passes
  • pnpm test passes
  • No new prop drilling introduced
  • No direct API calls in components

## Template 2: Full-Stack Next.js App

**What Claude gets wrong without this:** Mixing App Router and Pages Router patterns, incorrect server/client component boundaries, using client-side state for server data, ignoring streaming patterns.

```markdown
# Project

B2B SaaS product. Next.js 15 App Router, TypeScript, PostgreSQL via Drizzle ORM.
Authentication: Auth.js v5. Styling: Tailwind CSS 4.

## Commands

```bash
pnpm dev               # Next.js dev server
pnpm build             # production build
pnpm start             # production server
pnpm test              # Vitest unit/integration
pnpm test:e2e          # Playwright E2E
pnpm db:push           # push schema changes (dev only)
pnpm db:migrate        # generate and run migrations (production)
pnpm db:studio         # Drizzle Studio

React Server Components vs Client Components

Default to Server Components. Mark as Client Component only when:

  • Using hooks (useState, useEffect, useContext)
  • Using browser APIs
  • Using event listeners
  • Using third-party components that require client runtime
// Server Component (default, no directive needed)
export default async function UserList() {
  const users = await db.select().from(usersTable)
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}

// Client Component (explicit 'use client' required)
'use client'
export function SearchInput() {
  const [query, setQuery] = useState('')
  return <input value={query} onChange={e => setQuery(e.target.value)} />
}

Data Fetching

  • Fetch in Server Components — not in useEffect
  • Use fetch with Next.js caching options, or query the DB directly
  • Client-side data: TanStack Query with server actions as mutation endpoints
  • Route handlers (app/api/) only for third-party webhook endpoints

Database

// Query pattern — always use transactions for multi-step operations
const result = await db.transaction(async (tx) => {
  const user = await tx.insert(usersTable).values(data).returning()
  await tx.insert(auditTable).values({ userId: user[0].id, action: 'create' })
  return user[0]
})

Never use raw SQL. Schema changes go in db/schema.ts, then run pnpm db:migrate.

Off-Limits

  • pages/ directory — App Router only, no Pages Router
  • app/api/auth/ — managed by Auth.js, do not modify
  • db/migrations/ — auto-generated by Drizzle, do not edit

Anti-Patterns

  • No 'use client' at the layout level unless necessary
  • No data fetching in client components (except optimistic updates)
  • No server actions for reads — only for mutations
  • No direct DB access from client components
  • No environment variables without NEXT_PUBLIC_ prefix accessed client-side

## Template 3: Go REST API

**What Claude gets wrong without this:** Imperative error handling instead of Go idioms, mixing business logic into handlers, forgetting `context.Context` propagation, incorrect test table structure.

```markdown
# Project

REST API for [service description]. Go 1.23, Chi router, PostgreSQL via pgx.
Auth: JWT with RS256. Observability: OpenTelemetry → Grafana.

## Commands

```bash
go run ./cmd/api              # run the server
go test ./...                 # all tests
go test ./... -race           # with race detector (always run before PR)
go test -run TestUserService ./internal/users/
golangci-lint run             # lint (config in .golangci.yml)
go generate ./...             # run code generators (mockery, sqlc)
make migrate-up               # run database migrations
make migrate-create name=xxx  # create new migration

Project Structure

cmd/api/          # main package, only wiring
internal/
  users/          # domain package
    handler.go    # HTTP handler (Chi)
    service.go    # business logic
    repository.go # data access
    model.go      # domain types
  shared/
    middleware/
    database/
    config/
pkg/              # public packages (if any)
migrations/       # SQL migration files

Error Handling

All errors returned, never panicked (except in main for startup failures). Error wrapping: fmt.Errorf("users: get by id: %w", err).

// Good
func (s *Service) GetUser(ctx context.Context, id int64) (*User, error) {
    user, err := s.repo.GetByID(ctx, id)
    if err != nil {
        return nil, fmt.Errorf("users service: get user: %w", err)
    }
    return user, nil
}

// Handler maps domain errors to HTTP status codes
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
    id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
    user, err := h.service.GetUser(r.Context(), id)
    if err != nil {
        if errors.Is(err, ErrUserNotFound) {
            http.Error(w, "not found", http.StatusNotFound)
            return
        }
        http.Error(w, "internal error", http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(user)
}

Context Propagation

Pass context.Context as the first argument to every function that does I/O. Never store contexts in structs.

Testing

Table-driven tests in _test.go alongside source. Use testify for assertions. Integration tests: real DB (Docker), transaction rollback in each test.

func TestService_GetUser(t *testing.T) {
    tests := []struct {
        name    string
        id      int64
        want    *User
        wantErr error
    }{
        {"existing user", 1, &User{ID: 1, Name: "Test"}, nil},
        {"not found", 999, nil, ErrUserNotFound},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := service.GetUser(context.Background(), tt.id)
            assert.ErrorIs(t, err, tt.wantErr)
            assert.Equal(t, tt.want, got)
        })
    }
}

Anti-Patterns

  • No global state — use dependency injection through constructors
  • No init() functions — explicit initialization in main
  • No panic in handlers — always return errors
  • No naked returns from functions longer than 5 lines
  • Error messages: lowercase, no trailing period, descriptive
  • Never log.Fatal outside main

## Template 4: Nx Monorepo (TypeScript)

**What Claude gets wrong without this:** Running commands from the wrong directory, creating apps/libs in wrong locations, missing implicit dependencies in project graph.

```markdown
# Project

Nx monorepo with React apps and shared libraries. TypeScript throughout.
Apps: `web` (customer portal), `admin` (internal dashboard).
Libs: `ui` (design system), `data-access` (API clients), `util` (shared utils).

## Commands

```bash
# ALWAYS use nx commands, not raw package manager commands
nx run web:dev              # dev server for web app
nx run admin:dev            # dev server for admin app
nx run web:build            # build web app
nx run-many -t build        # build everything

nx run web:test             # test web app
nx run-many -t test         # test everything affected
nx affected -t test         # test only affected projects

nx generate @nx/react:component MyComponent --project=web
nx generate @nx/js:library my-lib --directory=libs/my-lib

Boundaries (enforced by Nx)

apps/ can import from: libs/
libs/ui can import from: (nothing in this repo)
libs/data-access can import from: libs/util
libs/util can import from: (nothing in this repo)

Do not violate these import boundaries — Nx lint will catch it, but do not introduce the violation.

Off-Limits

  • nx.json — workspace-level Nx config, do not modify
  • project.json in any project — targets/executors, do not modify
  • tsconfig.base.json — path aliases, do not modify

New Code Location

  • New React apps: nx generate @nx/react:app — never create manually
  • New shared components: add to libs/ui, not to an app’s src/
  • New API clients: add to libs/data-access
  • App-specific utilities: apps/{app}/src/lib/

Anti-Patterns

  • No imports across app boundaries (web cannot import from admin)
  • No shared state between apps (each app has its own Zustand store)
  • No ../../ imports that cross project boundaries — use @scope/lib-name paths

## Template 5: Open Source Library (TypeScript)

**What Claude gets wrong without this:** Breaking public APIs without version bumps, missing dual ESM/CJS exports, writing tests that test internals rather than public API.

```markdown
# Project

`@yourscope/library-name` — [one-line description].
Published to npm. Supports Node.js 20+ and browsers.

## Commands

```bash
pnpm build        # tsup build (ESM + CJS + types)
pnpm test         # Vitest
pnpm test:types   # tsd — verify public type signatures
pnpm lint         # ESLint + publint (package.json exports check)
pnpm size         # bundlesize check
pnpm release      # changeset version + npm publish (humans only)

Public API Rules

Public API = everything exported from src/index.ts.

Breaking changes require a major version bump. Breaking = any of:

  • Removing an export
  • Changing a function signature (parameters or return type)
  • Changing a type’s required properties
  • Changing default behavior

Additions are minor, fixes are patch. Use changesets: pnpm changeset.

Implementation Rules

Internal APIs: prefix with _ or use unexported names. Never rely on symbol names starting with _ being stable in the public API.

// Public (in src/index.ts exports)
export function createClient(options: ClientOptions): Client { ... }

// Internal (not exported from index.ts)
function _buildRequest(opts: BuildOptions): Request { ... }

Tree-Shaking

Every export must be individually tree-shakeable. Do not use object-wide imports.

// Good — each export is independent
export { feature1 } from './feature1'
export { feature2 } from './feature2'

// Bad — forces users to include everything
export * from './all-features'

Testing

Test the public API, not internals. If a test imports from anywhere other than src/index.ts, reconsider whether you are testing the right thing.

import { createClient } from '../src'  // always from public entry

describe('createClient', () => {
  it('creates a client with default options', () => {
    const client = createClient({ endpoint: 'http://test' })
    expect(client).toBeDefined()
  })
})

Compatibility

Target: ES2020. Do not use features that require polyfills in Node.js 20+. No bundler-specific features (no Vite/Webpack magic). Must work with node --experimental-vm-modules.


## Template 6: React Native / Expo App

**What Claude gets wrong without this:** Using web-only APIs, ignoring platform differences, missing Expo-specific patterns, wrong navigation structure.

```markdown
# Project

Expo 53 app (React Native 0.77). TypeScript. iOS and Android.
Navigation: Expo Router (file-based). State: Zustand. API: TanStack Query.

## Commands

```bash
npx expo start              # Metro bundler (scan QR with Expo Go)
npx expo start --ios        # iOS simulator
npx expo start --android    # Android emulator
npx expo run:ios            # native iOS build
npx expo run:android        # native Android build
pnpm test                   # Jest with jest-expo
eas build --profile preview # EAS cloud build (preview)

Platform Differences

Always check behavior on both platforms. Common differences:

  • Shadows: iOS shadowOffset/shadowOpacity, Android elevation
  • Text rendering: wrap all text in <Text>, never bare strings
  • Keyboard: KeyboardAvoidingView with behavior="padding" on iOS, behavior="height" on Android
// Platform-specific styling
const styles = StyleSheet.create({
  shadow: {
    ...Platform.select({
      ios: { shadowColor: '#000', shadowOffset: {width: 0, height: 2}, shadowOpacity: 0.1 },
      android: { elevation: 4 },
    }),
  },
})

File-based routing in app/. Each file = a screen.

app/
  (tabs)/           # Tab navigator group
    index.tsx       # Home tab
    profile.tsx     # Profile tab
  (auth)/
    login.tsx
    register.tsx
  _layout.tsx       # Root layout

Never use navigate() with hardcoded string paths. Use typed routes:

router.push('/(tabs)/profile')  // good
router.push('/profile')         // inconsistent — avoid

Anti-Patterns

  • No web-only APIs: no document, window, localStorage — use expo-* equivalents
  • No inline styles for repeated elements — use StyleSheet.create()
  • No FlatList vs ScrollView confusion: FlatList for long lists, ScrollView for short content
  • No async operations in render — use TanStack Query for data
  • No direct fetch — wrap in a query function with error handling

## Template 7: Chrome Extension (Manifest V3)

**What Claude gets wrong without this:** Using Manifest V2 APIs, mixing content scripts and service workers, not understanding the isolated world model.

```markdown
# Project

Chrome Extension (Manifest V3). TypeScript + React for popup/options pages.
Bundled with Vite + CRXJS plugin.

## Commands

```bash
pnpm dev          # dev mode with HMR
pnpm build        # production build to dist/
pnpm preview      # test against a static server

Extension Architecture

src/
  background/   # Service Worker (no DOM access)
    index.ts
  content/      # Content scripts (page DOM access, isolated JS world)
    index.ts
  popup/        # Popup UI (React app)
    App.tsx
  options/      # Options page (React app)
  shared/       # Shared utilities (no browser-specific APIs)
manifest.json

Service Worker Rules

The background service worker has NO DOM access. It cannot use window, document, or localStorage. For storage, use chrome.storage.local or chrome.storage.sync.

// Good — use chrome.storage in service worker
await chrome.storage.local.set({ key: value })
const result = await chrome.storage.local.get('key')

// Bad — not available in service worker
localStorage.setItem('key', value)  // undefined

Message Passing

Components communicate via chrome.runtime.sendMessage / chrome.tabs.sendMessage.

// Content script → background
chrome.runtime.sendMessage({ type: 'ANALYZE_PAGE', payload: { url } })

// Background listener
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'ANALYZE_PAGE') {
    // handle
    sendResponse({ status: 'ok' })
  }
  return true  // keep channel open for async response
})

Permissions

Never request more permissions than needed. If a feature needs a new permission, note it explicitly — it requires manifest.json and Chrome Web Store review.

Anti-Patterns

  • No eval() — blocked by Manifest V3 CSP
  • No remote code execution — all scripts must be bundled
  • No XMLHttpRequest in service workers — use fetch
  • No DOM manipulation from service workers
  • Never store sensitive data in chrome.storage without encryption

## Template 8: CLI Tool (TypeScript/Node.js)

**What Claude gets wrong without this:** Using process.exit() incorrectly, not handling stdin/stdout properly, missing cross-platform path handling.

```markdown
# Project

CLI tool: `mycli` — [what it does in 5 words].
Node.js 22+. TypeScript. Single binary via pkg/esbuild.

## Commands

```bash
pnpm dev                       # tsx watch mode
pnpm build                     # esbuild bundle
node dist/index.js --help      # test built output
pnpm test                      # Vitest
pnpm test:integration          # integration tests (spawns the CLI)

CLI Design Principles

  • Exit code 0: success. Exit code 1: user error. Exit code 2: internal error.
  • All errors go to stderr, output to stdout
  • Support --json flag for machine-readable output
  • Support NO_COLOR env variable
// Good error handling
try {
  await doSomething()
} catch (err) {
  console.error(`Error: ${err.message}`)
  process.exit(1)  // user-facing error
}

// Never throw in async without catching at top level

Output Formatting

Use chalk for colors (already installed). Check NO_COLOR before applying. Use ora for spinners on long operations. Stop spinner before writing output.

Path Handling

Always use path.resolve() for file paths. Never concatenate paths with string interpolation.

// Good
const configPath = path.resolve(process.cwd(), '.myconfig.json')

// Bad
const configPath = process.cwd() + '/.myconfig.json'

Anti-Patterns

  • No synchronous file reads for large files — use streams
  • No hardcoded ANSI escape codes — use chalk
  • No process.exit() in library code — only in the CLI entry point
  • No global mutable state — pass config through function arguments

## Template 9: Infrastructure / Terraform

**What Claude gets wrong without this:** Incorrect resource lifecycle, missing variable validation, unsafe data source patterns, wrong module interface design.

```markdown
# Project

AWS infrastructure managed with Terraform 1.9+. State in S3, locking in DynamoDB.
Environments: dev, staging, prod. Separate state files per environment.

## Commands

```bash
# Always work from environment directory
cd environments/dev
terraform init
terraform plan
terraform apply

# Lint and format
terraform fmt -recursive
tflint
terrascan scan

# Module development
cd modules/vpc
terraform init
terraform test   # Terraform test framework

File Structure

modules/             # reusable modules
  vpc/
    main.tf         # resources
    variables.tf    # inputs
    outputs.tf      # outputs
    versions.tf     # provider requirements
environments/        # per-environment configurations
  dev/
  staging/
  prod/

Safety Rules

Plan before apply — never terraform apply -auto-approve in staging or prod.

For any resource with prevent_destroy = false in staging/prod, add a comment explaining why it is not protected.

Never use count for resources that will need to be referenced by index — use for_each with a stable key instead.

Anti-Patterns

  • No hardcoded region — use variable
  • No terraform destroy without explicit human confirmation in PR
  • No data sources that can fail silently (data.aws_ami without filters)
  • No hardcoded credentials — use IAM roles and assume role
  • tags required on all resources: Environment, ManagedBy = "terraform", Project

Outputs

All modules must output their primary resource IDs. External consumers use outputs, never resource addresses.


## Template 10: Documentation Site (Astro/Docusaurus)

**What Claude gets wrong without this:** Breaking the content schema, adding non-existent components, wrong frontmatter, incorrect Markdown dialect.

```markdown
# Project

Documentation site built with Astro 5. Content in `src/content/docs/`.
Deployed on Cloudflare Pages. Search via Pagefind.

## Commands

```bash
pnpm dev          # dev server on :4321
pnpm build        # production build
pnpm preview      # preview built site

Content Rules

All docs pages are Markdown files in src/content/docs/. Required frontmatter:

---
title: "Page Title"           # required
description: "SEO description" # required
sidebar:
  order: 10                   # controls sidebar position (optional)
---

Do not add frontmatter fields not listed above — they will cause build errors.

Writing Style

  • Technical, direct, no marketing language
  • Code examples for every new concept
  • Use GitHub Flavored Markdown, not MDX (no JSX in docs content)
  • Images go in src/assets/ and are referenced as ../../assets/image.png

Navigation is auto-generated from directory structure. Do not edit astro.config.mjs navigation config — it is generated.

To add a new section: create a new directory in src/content/docs/. To reorder: change the sidebar.order frontmatter value.

Anti-Patterns

  • No HTML in Markdown content (except for tables — those are acceptable)
  • No absolute URLs for internal links — use relative links
  • No images wider than 1200px
  • No modifying src/components/ without understanding the component system
  • No JavaScript in content files — content is Markdown only

## Choosing Your Template

These templates are starting points, not final configurations. Start with the closest match, delete sections that do not apply to your stack, and add specifics about your actual setup.

The templates that consistently produce the most improvement are those with strong anti-patterns sections and explicit before-done checklists. If you can only add two things to an existing CLAUDE.md, make them those.

For the reasoning behind what makes these patterns effective, see [CLAUDE.md best practices 2026](/blog/claude-md-best-practices-2026). For Python-specific patterns, see [CLAUDE.md for Python projects](/blog/claude-md-for-python-projects).

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules