Claude Code10 min read · March 29, 2026

CLAUDE.md and Skill Files: The Complete Best Practices Guide for 2026

CLAUDE.md is loaded before every Claude Code session. Most developers treat it like a wish list and wonder why Claude ignores half of it. Here's the research-backed guide to writing one that actually works — and when to offload context to skill files instead.

# CLAUDE.md — Project: MyApp
## Stack
Next.js 15 · TypeScript strict · Supabase · Tailwind
## Commands
`npm run dev` · `npm test` · `npm run typecheck`
## Rules
NEVER commit .env · Named exports only
## Skills
See @.claude/skills/stripe/SKILL.md for payment flows

A lean, effective CLAUDE.md — under 50 lines, nothing wasted

In this article

  1. What is CLAUDE.md?
  2. The instruction budget problem
  3. What to actually include
  4. What to leave out
  5. The ideal structure
  6. Skill files: what they are and when to use them
  7. Skill file best practices
  8. When to use hooks instead
  9. Maintaining both over time
  10. A full worked example

What is CLAUDE.md?

CLAUDE.md is a markdown file that Claude Code reads automatically at the start of every session. Think of it as a persistent briefing document — a way to tell Claude things about your project that it can't infer from the code alone: how to run your tests, which patterns your team prefers, what it should never do, and where to look for domain-specific knowledge.

You can place CLAUDE.md files in several locations, and Claude loads all of them:

The file also supports imports. You can write @path/to/file to pull in other documents — a clean way to keep the main file short while linking to deeper references like authentication flow docs or architecture diagrams.

⚠️
The filename is case-sensitive. It must be exactly CLAUDE.md — uppercase CLAUDE, lowercase .md. Claude Code looks for this specific casing. A file named claude.md will be silently ignored.

The instruction budget problem

Here's the thing most guides don't tell you: Claude doesn't treat your CLAUDE.md as a permanent command list. It treats it as context — and context competes with everything else in the session for the model's attention.

Research from HumanLayer into Claude Code's internals found that frontier thinking models can follow roughly 150–200 instructions with reasonable consistency before compliance degrades. Smaller or non-thinking models follow even fewer. Here's the problem: Claude Code's own system prompt already occupies around 50 of those slots before you write a single line of your own.

That leaves you with roughly 100–150 instruction slots for your rules, preferences, and workflows combined. This isn't a lot. And Claude doesn't simply drop newer instructions when the budget runs out — it begins to follow all instructions less reliably, uniformly, across the whole file.

🧪
A quick test: Add the line "always address me as Mr. Tinkleberry" to your CLAUDE.md. Watch how many thousand tokens it takes before Claude stops using it. When that line disappears from Claude's attention, so does everything else in the file. That's the attention mechanism in practice.

The practical upshot: a bloated CLAUDE.md is worse than a short one. Every instruction you add dilutes every other instruction. HumanLayer keeps their own file under 60 lines. Anthropic's own documentation suggests keeping it "short and human-readable." The rule of thumb used by production teams is brutal: for every line, ask "would removing this cause Claude to make a mistake?" If the answer is no, cut it.

What to actually put in CLAUDE.md

With the instruction budget in mind, here's what earns its place in a lean, effective CLAUDE.md:

1

Project identity — one sentence

A single line that orients Claude to what this codebase is and what it does. "This is a Next.js 15 e-commerce app with Stripe payments and Supabase auth" tells Claude more than a paragraph of vague description. It shapes every code decision that follows.
2

Your exact run commands

Don't describe how to run things — give Claude the literal commands: npm run dev, npm test, npm run typecheck. Claude will use these verbatim when you ask it to verify its work. Ambiguous descriptions ("run the tests") produce inconsistent results.
3

Hard rules — the non-negotiables

These are your real guard rails: things that, if violated, cause actual damage. "NEVER commit .env files." "NEVER use raw SQL — always use the Drizzle ORM." "The Stripe webhook handler must validate signatures." Keep this section short. Three to five genuinely critical rules land harder than twenty diluted ones.
4

Architecture landmarks

A brief map of the codebase — where things live and why. "/app for pages, /components/ui for reusable elements, /lib for shared utilities." This prevents Claude from inventing file locations and helps it navigate confidently without asking you repeatedly.
5

Project-specific gotchas

The things a new developer would learn on their first PR review that aren't obvious from the code. "Product images are stored in Cloudinary, not locally." "We use PATCH not PUT for partial updates." "The auth middleware runs before every API route — don't duplicate auth checks." These are exactly what CLAUDE.md is for.
6

Skill file pointers — not the content itself

If you have skill files for specific workflows (Stripe integrations, deployment procedures, database migrations), point to them from CLAUDE.md using the @ import syntax rather than copying their content in. This keeps your main file short while making the knowledge available on demand.

What to leave out

Most of what people put in CLAUDE.md shouldn't be there. Here's what to cut:

Code style guidelines

Use a linter. ESLint and Prettier enforce style deterministically, every time, for free — without eating into your instruction budget. LLMs are comparably slow and expensive for work a $0 formatter handles instantly. Putting "use single quotes" in CLAUDE.md is a waste of three instruction slots.

Code snippets and examples

They go stale fast, and they eat context. Instead, use file:line references to point Claude at the authoritative version in your codebase. "See src/lib/auth.ts:45 for the session pattern" is more reliable than a copied snippet that may no longer match the real code.

Domain knowledge for occasional workflows

If Claude only needs to know about your Stripe integration when you're working on payments, don't load that knowledge every session. That's exactly what skill files are for. CLAUDE.md is for universal truths about the project, not specialist knowledge.

Things that only apply to you personally

Personal preferences (your editor setup, your preferred branch naming) belong in your global ~/.claude/CLAUDE.md, not the project file that gets checked into git.

Anything you can verify with a tool

TypeScript types, test passes, lint cleanliness — these are all checkable programmatically. Use hooks (see below) to enforce them automatically rather than instructing Claude to remember to check.

The ideal structure

There's no required format, but a consistent structure makes the file easier to maintain and easier for Claude to parse. Here's what production teams converge on:

# CLAUDE.md

## Project
Next.js 15 e-commerce app. TypeScript strict mode. Supabase auth and DB. Stripe payments. Deployed on Vercel.

## Commands
- `npm run dev` — start dev server (port 3000)
- `npm test` — run Jest unit tests
- `npm run test:e2e` — run Playwright end-to-end tests
- `npm run typecheck` — TypeScript strict check
- `npm run lint` — ESLint
- `npm run db:migrate` — run Drizzle migrations

## Architecture
- `/app` — Next.js App Router pages and layouts
- `/components/ui` — reusable UI components (shadcn/ui)
- `/lib` — shared utilities and server actions
- `/app/api` — API routes
- `/drizzle` — schema and migrations

## Hard rules
- NEVER commit .env or .env.local files
- NEVER use raw SQL — always use Drizzle ORM
- The Stripe webhook handler (/app/api/webhooks/stripe/route.ts) MUST validate signatures
- Use named exports, not default exports
- All server actions must validate session before any DB operation

## Gotchas
- Product images live in Cloudinary — never store locally
- The `useCart` hook is client-only — never import in server components
- Emails go through Resend, not SMTP — see @docs/email-setup.md

## Skills
- Stripe flows: @.claude/skills/stripe/SKILL.md
- Deployment: @.claude/skills/deploy/SKILL.md

Notice what isn't there: no code style rules (that's ESLint's job), no code snippets, no lengthy explanations of how Stripe works. The whole file is under 40 lines.

Skill files: what they are and when to use them

Skill files are the natural companion to CLAUDE.md. Where CLAUDE.md loads universally at the start of every session, skill files load on demand — either when you invoke them explicitly with a slash command, or when Claude detects they're relevant to the current task.

You create a skill by adding a directory to .claude/skills/ with a SKILL.md inside it:

.claude/
  skills/
    stripe/
      SKILL.md        ← loads when Claude works on payments
    deploy/
      SKILL.md        ← loads when Claude works on deployment
    database/
      SKILL.md        ← loads when Claude works on migrations

Each skill file uses YAML frontmatter to describe when it applies:

---
name: Stripe Payments
description: Payment processing, webhooks, and subscription management
user-invocable: true
allowed-tools: Read, Write, Bash
---

# Stripe Payments Skill

## Overview
This project uses Stripe for one-time payments and subscriptions.
The webhook handler is at /app/api/webhooks/stripe/route.ts.

## Key patterns
- Always use `stripe.webhooks.constructEvent()` to validate webhook signatures
- Test with Stripe CLI: `stripe listen --forward-to localhost:3000/api/webhooks/stripe`
- Use `STRIPE_SECRET_KEY` for server-side calls, `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` for client
- Price IDs live in /lib/stripe-prices.ts — never hardcode them

## Test cards
- Success: 4242 4242 4242 4242
- Decline: 4000 0000 0000 0002

## Common tasks
### Create a checkout session
See /lib/stripe.ts:createCheckoutSession for the canonical implementation.

The critical insight: skill files let you give Claude deep domain expertise precisely when it needs it, without that expertise polluting every other session. Your Stripe knowledge doesn't need to be in Claude's context when you're working on UI components.

💡
Skills vs MCPs: Many MCP servers can be replaced by well-written skill files. The advantage of skills is transparency — you can read exactly what Claude is being told to do. MCPs are black boxes. For internal workflows and project-specific knowledge, skills are often the better choice.

Skill file best practices

Write for the task, not the technology

Don't write a skill that explains how Stripe works in general. Write a skill that explains how your specific project uses Stripe. The distinction matters — Claude already knows how Stripe works. It doesn't know that your price IDs live in /lib/stripe-prices.ts or that your webhook endpoint expects raw body parsing.

Use file:line references, not code copies

Instead of pasting the canonical implementation into the skill file, point to it: "See /lib/auth.ts:createSession for the session creation pattern." The code in the file is always current. A pasted copy in a skill file will drift.

Inject dynamic context with shell commands

Skill files support inline shell commands using !`command` syntax. Claude runs the command on invocation and sees only the result — not the command itself. This is useful for injecting the current state of something: the current git branch, the list of active feature flags, the output of a health check.

---
name: Database Status
description: Check current migration state before any DB work
---

# Database Skill

## Current migration state
!`npm run db:status`

## Rules
- Always check migration state before generating new migrations
- Use `npm run db:migrate` to apply, never run SQL directly

Keep the description accurate — Claude uses it for auto-discovery

The description field in your YAML frontmatter is how Claude decides whether to load a skill automatically. If the description doesn't match the kind of tasks you want it to trigger on, Claude won't load it when you need it. Be specific and task-oriented: "Stripe payment processing, webhooks, and subscription management" not "payments."

Use PreToolUse hooks to measure which skills are actually firing

You can add a hook that logs whenever a skill is invoked, which lets you identify skills that are undertriggering (Claude isn't loading them when it should) or over-triggering (loading all the time when they're not relevant). Without this visibility, you're guessing.

When to use hooks instead of instructions

CLAUDE.md instructions are advisory — Claude follows them roughly 80% of the time. If something must happen every single time with zero exceptions, use a hook instead. Hooks are deterministic.

Use hooks for

Running Prettier after every file edit
Running ESLint after Claude writes code
TypeScript checks before committing
Blocking writes to protected directories
Auto-formatting on every save

Use CLAUDE.md for

Code architecture preferences
Project-specific gotchas and warnings
Where to find things in the codebase
Preferred patterns and conventions
Non-negotiable rules (with emphasis)

A common hook pattern: run Prettier automatically on every file Claude edits. Add this to.claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write $CLAUDE_TOOL_INPUT_PATH"
          }
        ]
      }
    ]
  }
}

Now Claude generates well-formatted code, and the hook handles the last 10% to keep your CI happy — without any CLAUDE.md instruction reminding Claude to "format code properly."

Maintaining both over time

Both CLAUDE.md and skill files compound in value over time — but only if you maintain them like code, not documentation.

Treat CLAUDE.md like a codebase. Check it into git. Review it when sessions go wrong. When Claude does something unexpected, ask: is this a CLAUDE.md problem (missing rule, unclear gotcha) or an instruction budget problem (file too long, rule getting lost)? Prune regularly. The file should shrink over time as you offload domain knowledge to skill files and enforce style with hooks.

Watch for the "still ignoring" signal. If Claude keeps doing something you've explicitly prohibited, the file is either too long or the rule is too vaguely worded. Try adding emphasis — "IMPORTANT:" or "YOU MUST" — before genuinely critical rules. If that doesn't work, the file is probably too bloated overall and the rule is getting drowned out.

Skill files drift — review them on major dependency upgrades. A Stripe skill written for Stripe SDK v12 will give Claude incorrect patterns after you upgrade to v14. Skill files that reference specific APIs need to be updated when those APIs change. File:line references help — they point to the live code rather than a copy — but the overall structure of a skill still needs human review.

👥
Team collaboration tip: Check your project CLAUDE.md into git and review it the same way you review any config file. Every team member benefits from accumulated project knowledge, and PRs that update CLAUDE.md should explain the "why" — what went wrong in a session that prompted the change.

A full worked example for a Next.js SaaS project

Here's what the complete CLAUDE.md + skill file structure looks like for a production Next.js app:

# Root CLAUDE.md (~35 lines)

## Project
Next.js 15 SaaS app. TypeScript strict. Supabase. Stripe subscriptions. Resend email. Deployed on Vercel.

## Commands
- `npm run dev` — dev server (localhost:3000)
- `npm test` — Jest (unit)
- `npm run e2e` — Playwright
- `npm run typecheck` — strict TS check
- `npm run db:migrate` — apply Drizzle migrations

## Architecture
- `/app` — App Router pages and layouts
- `/components/ui` — shadcn/ui components (never modify directly)
- `/lib` — server utilities, actions, helpers
- `/app/api` — API routes and webhook handlers
- `/drizzle` — schema, migrations

## Hard rules
- NEVER commit .env or .env.local
- NEVER use raw SQL — Drizzle ORM only
- NEVER import server-only modules in client components
- Stripe webhook: always validate signature (see @.claude/skills/stripe/SKILL.md)
- Named exports only — no default exports

## Gotchas
- File uploads go to Supabase Storage, not /public
- useAuth() is client-only — never call in server components
- Email uses Resend SDK, not nodemailer
.claude/skills/
  stripe/SKILL.md       ← payment flows, webhook patterns, test cards
  database/SKILL.md     ← migration workflow, schema conventions
  deploy/SKILL.md       ← Vercel deploy steps, env var checklist
  email/SKILL.md        ← Resend templates, sending patterns

The result: a CLAUDE.md that loads fast, stays within the instruction budget, gives Claude everything it needs to work confidently in the codebase — and offloads specialist knowledge to skills that only load when relevant.

🚀
Before you write a single line of CLAUDE.md, validate that you're building the right thing. Valid8it generates a Reddit-sourced validation report, ranked product ideas, a product specification, and a ready-to-use Claude Code starter pack — including a project-specific CLAUDE.md — in under three minutes. Try it free →

Frequently asked questions

How long should a CLAUDE.md file be?

As short as possible. Production teams and Anthropic's own documentation recommend keeping it under 200 lines, with the most effective files coming in well under 100. HumanLayer keeps theirs under 60 lines. Every line competes with every other line for the model's attention — shorter files produce more reliable instruction-following.

What is the difference between CLAUDE.md and a skill file?

CLAUDE.md loads at the start of every session and should contain universal project truths — run commands, hard rules, architecture landmarks. Skill files load on demand when relevant to a specific task. Domain knowledge that Claude only needs sometimes (Stripe integration details, deployment procedures, database migration patterns) belongs in skill files, not CLAUDE.md.

Why does Claude keep ignoring my CLAUDE.md rules?

Usually one of two things: the file is too long and the rule is being crowded out by the attention mechanism, or the rule is phrased too vaguely. Try shortening the file significantly and adding "IMPORTANT:" or "YOU MUST" before genuinely critical rules. If the rule must be followed without exception, consider making it a hook instead of an instruction.

Should I put code examples in CLAUDE.md?

No. Code examples go stale quickly and eat instruction budget. Instead, use file:line references to point Claude at the authoritative implementation in your codebase. "See src/lib/auth.ts:45 for the session pattern" is more reliable than a pasted snippet that may no longer match the real code.

Can I have multiple CLAUDE.md files in one project?

Yes. Claude supports CLAUDE.md files at the project root, in subdirectories, and in your home folder. The project root file is checked into git and shared with your team. Subdirectory files load on demand when Claude works in those directories — useful for monorepos where each package has its own conventions.

What is the @import syntax in CLAUDE.md?

CLAUDE.md supports importing other files using @path/to/file syntax. This lets you keep the main file short while linking to deeper references. For example: "Git workflow: @docs/git-instructions.md" — Claude will read the linked file when it needs that context.

Valid8it

Get a Claude Code starter pack with your validation

Valid8it generates a project-specific CLAUDE.md, scaffold, and skill files alongside your Reddit validation report. Free to start.

Start free →