CLAUDE.md Best Practices: A Template for Claude Code Projects

Relia Software

Relia Software

CLAUDE.md is a Markdown file at the root of repository (or in .claude/CLAUDE.md) that Claude Code automatically loads into context at the start of every session.

What is CLAUDE.md File? How to Write a Good CLAUDE.md file?

CLAUDE.md is the instructions file Claude Code reads at the start of every session to learn your project's rules. Treat it as a constitution (short, enforceable constraints) rather than documentation (long explanations). A good CLAUDE.md is usually under 200 lines, contains only rules that should still be true six months from now, and replaces architectural drift across sessions with consistent, on-pattern output.

If you've used Claude Code on a project for more than a few weeks, you've probably noticed the same problem: each session feels like onboarding a new engineer. Claude writes mostly-functional code, but every fresh conversation tends to invent a slightly different pattern for the same problem.

The fix isn't a better model or a longer prompt. It's a better CLAUDE.md.

This blog shows you what to put in CLAUDE.md, what to leave out, a complete copy-pasteable template, common mistakes, and my actual workflow in using Claude Code.

What is CLAUDE.md?

CLAUDE.md is a Markdown file at the root of your repository (or in .claude/CLAUDE.md) that Claude Code automatically loads into context at the start of every session. It is the canonical place to store project-specific rules, constraints, and conventions that Claude respects when generating or editing code.

Three things to know up front:

  • Auto-loaded: you don't need to mention the file in prompts. Claude Code injects it into the system context.
  • Hierarchical: Claude Code reads ~/.claude/CLAUDE.md (user-level), then the repo's CLAUDE.md (project-level), then any CLAUDE.md files in subdirectories. Lower files override higher ones.
  • Generated on init: running /init inside Claude Code scaffolds a starter file. That scaffold is a useful skeleton, but it should not be your final version.

Architectural Drift: The Problem Before CLAUDE.md

My project itself was a typical enterprise Next.js application. It served multiple countries and languages, included public SEO-focused pages, and integrated with OpenSearch, Google Maps, Google Geolocation, and several internal backend services.

None of those technologies were particularly unusual. What made the system complex was the number of architectural decisions that had already been made.

For example, we had agreed that:

  • React components should never talk directly to external services.
  • All third-party integrations needed to go through a Backend-for-Frontend layer.
  • OpenSearch access belonged exclusively inside search services.
  • Business logic should live in domain services rather than route handlers.

These rules weren't complicated, but they were important because they kept the codebase consistent.

The problem was that those rules existed everywhere and nowhere at the same time. Some decisions were documented in ADRs. Others were buried inside architecture diagrams. Some lived in onboarding documents. A few only existed in previous conversations with Claude. As a human engineer, I could piece those rules together because I had been involved in the project. But, Claude couldn't.

The result was a form of architectural drift. Imagine asking Claude to implement a new search feature several months apart in different sessions. One implementation might look like this:

// app/search/page.tsx

typescript
export async function SearchPage() {
  const results = await openSearchClient.search(...)
}

A different session might decide to create a utility:

// lib/search.ts

typescript
export async function searchProducts() {
  return openSearchClient.search(...)
}

Yet another might introduce an entirely new service abstraction:

// services/search/SearchService.ts

typescript
export class SearchService {
  async search() {
    return this.client.search(...)
  }
}

Individually, none of these implementations are wrong. The problem is that together they create three different patterns for solving the same problem.

Treating CLAUDE.md As a Constitution

When a software project reaches a certain size, consistency becomes more valuable than individual implementation choices. At that stage, I would rather have ten developers follow the same pattern than ten developers create ten different patterns that are all technically valid.

This is when I stopped treating CLAUDE.md as ordinary project documentation and started treating it as a project constitution for Claude Code.

The difference sounds small, but it changed how I used Claude in day-to-day development.

Here is the difference between treating CLAUDE.md as documentation vs constitution:

DocumentationConstitution
Explains how the system worksDefines what must remain true
Answers: "what does this code do?"Answers: "what rules govern any new code?"
Long, narrative, illustrativeShort, declarative, enforceable
Grows with featuresStable across features
Audience: humans onboardingAudience: an agent making decisions

What to Put in CLAUDE.md File?

Architecture rules

Hard constraints on where code may and may not live.

markdown
## Architecture Rules
- React components never call external services directly.
- All third-party integrations go through BFF routes.
- OpenSearch access belongs only in search services.
- Business logic belongs in domain services.
- Feature state stays inside feature folders.

Notice: No diagrams, no explanations, no paragraphs describing why these decisions were made. They're simply rules. That distinction matters because Claude is exceptionally good at following constraints. The more ambiguity you remove, the more consistent the output becomes.

Coding standards

Language- and library-level conventions that are universal across the repo.

markdown
# Coding Standards

- TypeScript strict mode.
- No any types.
- Zod validation for external payloads.
- Prefer Server Actions over custom API routes.
- Use structured logging.

Again, these aren't suggestions. They're expectations. When Claude reads them before starting work, it doesn't have to infer project conventions from existing code. The rules are already explicit.

Decision records

One section I found surprisingly valuable was decision records. Most projects accumulate architectural decisions that make perfect sense today but become mysterious six months later. In our case, one of those decisions involved OpenSearch.

markdown
# Decision Records

We use OpenSearch instead of database search.

Reason:
- Geo search support
- Better ranking control
- Multilingual indexing

Do not bypass OpenSearch for user-facing search features.

This is a small rule, but it prevents an entire category of future mistakes. Without it, Claude might reasonably decide to implement search using direct database queries because it has no way of knowing the tradeoffs that led to OpenSearch being selected in the first place.

File organization

A flat map of where things go. Keep it readable in 10 seconds.

markdown
## File Organization
src/
├── pages/api/   # BFF routes — the only place that talks to external services
├── app/         # Next.js App Router pages and layouts (UI only)
├── components/  # Reusable React components (no service calls)
├── services/    # Domain services (search, pricing, geo, etc.)
├── lib/         # Pure utilities (no I/O, no React)
├── middleware/  # Auth, i18n, logging
├── types/       # Shared TypeScript types
└── config/      # Environment and feature flags

Commands

The handful of commands Claude should know how to run. Skip the README dump; list only what's actually needed during a session.

markdown
## Commands
- `npm run dev` — start dev server on http://localhost:3000
- `npm test -- --coverage` — run tests with coverage
- `npm run lint:fix` — auto-fix lint issues
- `npm run typecheck` — TS type check (run before declaring a task done)

Here is a complete CLAUDE.md template. Copy this, replace the bracketed parts, and you have a working constitution in 15 minutes.

markdown
# CLAUDE.md

## Project Overview
[Two sentences. What the product is, what the stack is. No marketing copy.]
Example: "Enterprise Next.js storefront serving 14 countries and 9 languages.
Stack: Next.js 15 App Router, TypeScript strict, OpenSearch, Postgres, Tailwind."

## Commands
- `npm run dev` — dev server on http://localhost:3000
- `npm test` — Vitest unit/integration tests
- `npm test -- --coverage` — coverage report
- `npm run lint:fix` — auto-fix lint
- `npm run typecheck` — strict type check (must pass before any task is "done")
- `npm run db:migrate` — apply pending DB migrations

## Architecture Rules
- React components never call external services directly.
- All third-party integrations go through BFF routes in `src/pages/api/*` or Server Actions.
- OpenSearch access belongs only in `services/search/*`.
- Business logic lives in domain services, not in route handlers or components.
- Feature state and types stay inside the feature folder; no cross-feature imports.
- No global state libraries (Redux, Zustand, etc.) — prefer Server Components + URL state.

## Coding Standards
- TypeScript strict mode. No `any`. No `@ts-ignore` without an issue link.
- Validate all external input (HTTP, env, file) with Zod at the boundary.
- Server Actions preferred over custom API routes for mutations.
- Structured logging only: `logger.info({ event, userId, ... })`.
- Errors thrown across boundaries must extend `AppError` and carry an error code.
- Date handling: `date-fns` in UTC; never construct `new Date()` for business logic.
- Imports: absolute paths from `src/`; no `../../..` chains.

## Decision Records
- **OpenSearch over Postgres FTS** for user-facing search. Geo, ranking, multilingual indexing. Do not bypass.
- **Server Components by default**, Client only when interactivity requires it.
- **Tailwind, no CSS Modules.** Design-system consistency.
- **i18n via `next-intl`.** All user-facing strings via `t()`; never inline literals.
- **Auth via NextAuth with JWT sessions.** Do not introduce a parallel session system.

## File Organization
src/
├── app/              # Next.js App Router pages and layouts (UI only)
├── pages/api/        # BFF routes (the only place that talks to externals)
├── components/       # Reusable UI components (no service calls)
├── features/         # Feature folders — components + hooks + types co-located
├── services/         # Domain services (search, pricing, geo, payments)
│   ├── search/       # OpenSearch access lives here exclusively
│   ├── pricing/
│   └── geo/
├── lib/              # Pure utilities (no I/O, no React)
├── middleware/       # Auth, i18n, logging
├── types/            # Shared TypeScript types
└── config/           # Environment and feature flags

## Testing Rules
- Every domain service has unit tests; mocks for I/O only.
- API routes have integration tests with a test database.
- React components: test behavior with Testing Library, not snapshots.
- A task is not done until `npm run typecheck && npm test` is clean.

## Working with Claude
- Never bypass these rules to "make it work." Propose an exception in chat first.
- When unsure between two valid patterns, ask before generating.
- Prefer editing existing files over creating new ones.
- Do not commit unless explicitly asked.
- Update this file when you add a rule we should keep.

What Do NOT Put in CLAUDE.md?

The first draft of every CLAUDE.md becomes a wiki if you don't keep these out:

  • Sprint tasks, ticket lists, in-progress work. These belong in your tracker, not in Claude's permanent context.
  • Feature explanations and product copy. If a reader needs background on what the feature does, put it in the feature folder's README.
  • Architecture diagrams and long rationales. A diagram is a documentation artifact, not a rule. Link to it if needed.
  • Onboarding instructions. Different audience. Use ONBOARDING.md.
  • Historical context. "We used to do X but switched to Y" is interesting; the only thing Claude needs is "Use Y."
  • Style preferences your linter already enforces. Don't restate what ESLint/Prettier will catch, that's noise.

A useful test: if a rule wouldn't still matter six months from now, it doesn't belong in CLAUDE.md.

Eventually I realized that the best constitutions are boring. They don't contain everything, only the things that should still matter six months from now. Sprint tasks, meeting notes and feature requirements don't belong there. Instead, architectural constraints, coding standards, refactoring expectations do.

Common Mistakes (and How to Avoid Them)

Mistake 1: Writing it like documentation.

  • Symptom: paragraphs of "this system works by…".
  • Fix: convert every paragraph into bullet rules or delete it.

Mistake 2: Including sprint or feature work.

  • Symptom: "Current sprint: implement checkout v2."
  • Fix: move to your tracker. CLAUDE.md is for things that should still be true next year.

Mistake 3: Restating what your linter enforces.

  • Symptom: "Use semicolons. Use single quotes."
  • Fix: configure ESLint/Prettier and delete those lines. Keep CLAUDE.md for what tools cannot check.

Mistake 4: One giant CLAUDE.md instead of nested ones.

  • Symptom: monorepo where mobile, web, and infra rules collide.
  • Fix: put a CLAUDE.md in each sub-package; let inheritance do the work.

Mistake 5: Vague rules.

  • Symptom: "Write clean code" is not a rule, it's a wish.
  • Fix: "Functions over 40 lines must be split or justified in a comment" is a rule.

Mistake 6: No "Decision Records" section.

  • Symptom: every few weeks Claude re-proposes a pattern you rejected six months ago.
  • Fix: capture the decision (one sentence + reason) so it survives sessions.

Proper Workflow I Use

Today, whenever I start a new project, one of the first commands I run inside Claude Code is /init.

the first commands I run inside Claude Code
The first commands I run inside Claude Code.

Claude generates an initial CLAUDE.md, which gives me a useful starting point. I then spend twenty minutes replacing generic guidance with project-specific rules. Most of the effort goes into identifying the constraints that I know I'll eventually repeat during code reviews.

Over time, the constitution evolves alongside the project. Whenever I notice myself giving the same feedback repeatedly, I ask a simple question: "Is this actually a project rule?" If the answer is yes, it probably belongs in CLAUDE.md.

markdown
# CLAUDE.md
## Project Overview
This is an enterprise Next.js application serving multiple countries and languages. It includes public SEO-focused pages and integrates with several external services: OpenSearch, Google Maps, Google Geolocation, and internal backend services.
## Commands
### Development
```bash
npm run dev          # Start development server (typically on localhost:3000)
```
### Testing
```bash
npm test -- --coverage           # Generate coverage report
```
### Code Quality
```bash
npm run lint:fix     # Auto-fix linting issues
```
## Architecture Principles
### Backend-for-Frontend Pattern
- **React components never communicate directly with external services** (OpenSearch, Google Maps, Geolocation, backend services)
- . . .
### Service Layer Organization
- . . .
### Multi-Country & Multi-Language Support
- . . .
### External Service Integration
- . . .
## File Organization
```
src/
├── pages/                    # Next.js page routes and API routes (BFF layer)
├── components/               # React components (UI only, no service calls)
├── services/
├── lib/                     # Utilities, helpers, shared functions
├── middleware/              # Next.js middleware for auth, logging, etc.
├── types/                   # TypeScript types and interfaces
└── config/                  # Configuration files
```
## Key Constraints


1. **No direct external service calls from React components** — Route through services and API routes
2. **OpenSearch queries only in search services** — Other parts of the app access search results through domain services
3. **Business logic in domain services** — Not in route handlers or components
4. **Multi-country/language aware** — Services should handle regional variations appropriately
## Testing Strategy
- . . .
## Common Development Tasks
### Adding a New Feature
1. . . .
### Integrating a New External Service
1. . . .
### Debugging Service Integration Issues
- . . .

How CLAUDE.md Changes Your Prompts?

The strongest signal that your constitution is working: your prompts get shorter.

Before, an effective prompt looked like this:

"Add a search feature. Remember we use OpenSearch, not the database, and route everything through a service in services/search. The component should not call OpenSearch directly, use the BFF route. Validate inputs with Zod. Use TypeScript strict, no any. Add tests."

After CLAUDE.md is doing its job, the same prompt becomes:

"Add a search feature."

That isn't because the model got smarter. It's because the rules became persistent.

Conclusion

Looking back, the biggest improvement in my AI-assisted development workflow didn't come from a larger context window, a better model or a more sophisticated prompting technique. It came from recognizing that every mature software project already operates according to a set of unwritten rules.

The problem is that humans can learn those rules through experience. AI cannot. Once I started writing those rules down in a place Claude could consistently see, the generated code stopped merely working. It started feeling like it belonged in the project.

  • coding
  • automation
  • development