Skip to content

How to Create a Production CLAUDE.md for Web Development

A CLAUDE.md is where you answer a project's unstated questions once, in a file, instead of in every prompt. Here is what belongs in one, what does not, and how to tell whether yours is working.

Claude Code Guides: How to Create a Production CLAUDE.md. A document titled CLAUDE.md with a checklist of rules.

What CLAUDE.md actually is

CLAUDE.md is a Markdown file that Claude Code loads automatically at the start of every session in your project. Its contents become part of the context for every request you make, without you pasting anything.

That is the whole mechanism. What makes it worth writing carefully is a consequence of how agentic coding fails.

An agent working in your repository has to make dozens of small decisions you never stated: which HTTP client to use, whether errors throw or return, how files are named, whether new dependencies are acceptable, what "done" means. It will make each of those decisions reasonably. Reasonable is not the same as consistent with your project, and inconsistency compounds — a session that guessed wrong about error handling on Monday produces code that the Wednesday session then imitates.

CLAUDE.md is where you answer those questions once, in a file, instead of in every prompt or not at all.

The useful mental model: it is the onboarding document you would write for a competent engineer joining your team who is going to start committing on their first day. Not a tutorial, not a description of the codebase — the things they could not work out by reading it, and the things they would get wrong if nobody told them.

Where the files live and which wins

Claude Code reads memory from several locations, and they combine rather than replace one another. From broadest to narrowest:

Scope Location Use it for
Enterprise A managed system path, deployed by IT Organisation-wide policy
Project ./CLAUDE.md at the repo root Team conventions — committed to git
User ~/.claude/CLAUDE.md Your personal preferences, every project
Directory CLAUDE.md in a subdirectory Rules specific to that part of the tree

The project file is the one that matters most, and the one this article is about. It lives at the repository root and it is committed to version control. That is not incidental — the entire value of a project CLAUDE.md is that everyone on the team, and every future session, gets the same instructions. A conventions file that only exists on your machine is a preference, not a convention.

Subdirectory files are loaded when work happens in that part of the tree. This is what makes monorepos manageable — the frontend package can carry its own rules without every backend session paying for them.

Two commands are worth knowing. /init bootstraps a CLAUDE.md by analysing the project — a reasonable starting point that you should then rewrite, because what it produces is largely descriptive and the value is in the prescriptive parts. /memory opens the memory files for editing during a session.

Four memory scopes loaded together: enterprise policy from a managed system path, project memory from ./CLAUDE.md, user memory from ~/.claude/CLAUDE.md, and directory memory from a CLAUDE.md inside a subtree. Project is highlighted.
The four scopes combine rather than replace one another. The project file, highlighted, is the one your team shares.

The test for what belongs in it

One question decides almost every case:

Could this be worked out by reading the codebase?

If yes, leave it out. Directory structures, what each module does, the list of dependencies, how a function works — all of that is visible in the code, and the code is the source of truth. Documenting it in CLAUDE.md buys you nothing and creates a second thing to keep in sync, which will drift, and drifted instructions are worse than absent ones.

If no, it belongs. Conventions, prohibitions, non-obvious constraints, decisions with history behind them, the commands that are not discoverable, and the mistakes people reliably make.

Belongs in CLAUDE.md Does not
"Use the shared apiClient; never call fetch directly" A description of what apiClient does
"Never edit files in generated/ — they are rebuilt" A list of the files in generated/
"Run npm run check before saying a task is complete" The contents of package.json
"We use tabs, not spaces" Anything your formatter already enforces
"Ask before adding a dependency" The current dependency list
"Migrations are additive-only; never drop a column in the same release" How to write a migration in general

The fourth row deserves emphasis. If a linter or formatter enforces something automatically, it does not need to be in CLAUDE.md — it will be corrected mechanically. Spend the space on things no tool checks.

Why length is the main failure mode

The common failure is not an incomplete CLAUDE.md. It is a bloated one.

Claude Code's own documentation suggests keeping memory files under roughly 200 lines. That guidance exists because of how instructions actually behave in a long context: as the file grows, individual rules get less attention, not more. A 600-line file where forty of the lines matter is worse at communicating those forty lines than a 90-line file containing only them. You have diluted your own signal.

There is a second cost. The file is loaded into every session, so every line is paid for on every request, forever. A paragraph explaining your company's history is a rounding error once and a real expense across a thousand sessions.

A good production CLAUDE.md for a web project is typically 80 to 150 lines. If yours is longer, the fix is almost never to trim wording — it is to find the sections that describe rather than instruct, and delete them entirely.

Anatomy of a production CLAUDE.md

Ten sections cover essentially every web project. Not all are needed every time; skip the ones with nothing real to say rather than filling them with generalities.

  1. Project — two or three sentences on what this is and who it is for
  2. Stack — the technologies and versions, no commentary
  3. Commands — how to run, build, test, check, deploy
  4. Conventions — how code in this project is written
  5. Architecture rules — the structural decisions that must hold
  6. Do not — the explicit prohibitions
  7. Quality bar — what "done" means here
  8. Before finishing — the checklist for every task
  9. Gotchas — the non-obvious things that catch people
  10. Ask first — where the agent should stop and check

Sections six, eight, and ten do the heaviest lifting, and they are the three most often missing.

The template, section by section

Project

## Project

Customer-facing storefront for a single digital product. Static
marketing pages plus a checkout flow. Traffic is roughly 70% mobile.
Optimised for conversion and organic search, in that order.

Three sentences. The mobile share and the stated priority order both change decisions later — they are not filler. Anything that does not change a decision is filler.

Stack

## Stack

- TypeScript 5.x, strict mode
- Astro 4.x, static output
- Tailwind CSS 3.x
- Playwright for end-to-end tests
- Deployed to Cloudflare Pages

A list. No explanation of what Astro is. Version families matter because they change what APIs exist; patch versions do not and will make the file stale.

Commands

## Commands

- `npm run dev` — local dev server on :4321
- `npm run build` — production build to dist/
- `npm run check` — types, lint, and format in one pass
- `npm run test` — Playwright, requires a build first
- `npm run deploy` — production deploy; ask before running

This is the section that pays for itself immediately. Without it, every session spends a turn reading package.json to find out how to run the tests. Note the last line: the prohibition lives with the command it applies to, where it will actually be read.

Conventions

## Conventions

- Components in `src/components/`, PascalCase files
- Routes in `src/pages/`, kebab-case files
- Every colour, spacing, and font value comes from the Tailwind
  config. Never hard-code a hex value or a pixel value that exists
  as a token.
- No inline styles
- Named exports only, no default exports
- Errors return a Result type; they do not throw across a
  module boundary

Only conventions no tool enforces. Whether your formatter would fix it is the filter.

Architecture rules

## Architecture rules

- All data fetching happens at build time. This site ships no
  client-side data fetching; if a feature seems to need it, raise it
  rather than adding it.
- JavaScript is progressive enhancement. Every page must be usable
  with JS disabled.
- No new runtime dependency without discussing it first. Build-time
  dependencies are lower risk but still worth mentioning.

These are the decisions that shape everything downstream. "If a feature seems to need it, raise it" is important — a rule stated without an escape hatch will eventually be violated silently, because the alternative was to fail. Naming the escape hatch keeps the violation visible.

Do not

## Do not

- Edit anything in `dist/` — build output
- Edit `src/generated/` — regenerated from the schema
- Commit to `main` — always branch
- Add a CSS framework, UI kit, or component library
- Use `!important`
- Add analytics, tag managers, or third-party scripts without asking
- Change the build configuration without explaining why first

Prohibitions are the highest-value lines in the file because they are the ones that cannot be inferred. Every reasonable-looking action you did not want needs to be here or it will eventually happen.

Quality bar

## Quality bar

- WCAG 2.2 AA. Semantic HTML, visible focus states, keyboard
  operable, 4.5:1 contrast on text.
- Works from 320px wide with no horizontal overflow
- Every page has a unique title and meta description
- Images have explicit width and height
- Lighthouse performance 90+ on mobile for the templates in
  `docs/perf-baseline.md`

Concrete and checkable. "Make it accessible" is a sentiment; "4.5:1 contrast on text" is a test. Anything in this section that cannot be verified is decoration.

Before finishing

## Before finishing any task

1. Run `npm run check` and show me the output
2. Run `npm run build` and confirm it succeeds
3. Tell me what you changed and why
4. Tell me what you did not verify

Do not say a task is complete without steps 1 and 2. If a check
fails, fix it or tell me — do not report success.

This is the single most valuable section in the file. It converts "done" from a self-assessment into a procedure with evidence attached. Step 4 is the one people leave out and the one that surfaces the most.

Gotchas

## Gotchas

- The `dev` server does not reflect changes to `astro.config.mjs`.
  Restart it after editing that file.
- Cloudflare Pages is case-sensitive; macOS local dev is not. A
  wrong-case import works locally and 404s in production.
- The contact form posts to a Worker, not a route in this repo. It
  is in `../worker/`.
- Tests need a fresh build. `npm run test` against a stale `dist/`
  passes while testing the previous version.

Each of these is a real hour someone lost. That is the bar for inclusion — not "might be confusing", but "has actually cost someone time".

Ask first

## Ask before

- Adding any dependency
- Changing anything under `src/checkout/`
- Modifying the build or deploy configuration
- Deleting files
- Any change touching pricing, legal pages, or policy text
- Anything that would change a published URL

Draw this boundary around blast radius rather than difficulty. Changing a URL is a two-character edit that can cost you months of accumulated search equity — it belongs here; a hard refactor of an internal module does not.

The complete file

Assembled, the whole thing is short. This is roughly 95 lines, which is where a real one should land:

# CLAUDE.md

## Project
Customer-facing storefront for a single digital product. Static
marketing pages plus a checkout flow. Traffic is ~70% mobile.
Optimised for conversion and organic search, in that order.

## Stack
- TypeScript 5.x, strict mode
- Astro 4.x, static output
- Tailwind CSS 3.x
- Playwright for end-to-end tests
- Deployed to Cloudflare Pages

## Commands
- `npm run dev` — local dev server on :4321
- `npm run build` — production build to dist/
- `npm run check` — types, lint, format
- `npm run test` — Playwright; requires a build first
- `npm run deploy` — production deploy; ask before running

## Conventions
- Components in `src/components/`, PascalCase
- Routes in `src/pages/`, kebab-case
- Every colour and spacing value from the Tailwind config.
  Never hard-code a value that exists as a token.
- No inline styles
- Named exports only
- Errors return a Result type; do not throw across module boundaries

## Architecture rules
- All data fetching at build time. No client-side data fetching;
  if a feature seems to need it, raise it rather than adding it.
- JavaScript is progressive enhancement. Every page usable with
  JS disabled.
- No new runtime dependency without discussing it first.

## Do not
- Edit `dist/` or `src/generated/`
- Commit to `main` — always branch
- Add a CSS framework, UI kit, or component library
- Use `!important`
- Add analytics or third-party scripts without asking
- Change build configuration without explaining why first

## Quality bar
- WCAG 2.2 AA: semantic HTML, visible focus, keyboard operable,
  4.5:1 contrast on text
- Works from 320px with no horizontal overflow
- Unique title and meta description per page
- Explicit width and height on images
- Lighthouse mobile performance 90+ on the templates listed in
  docs/perf-baseline.md

## Before finishing any task
1. Run `npm run check` and show me the output
2. Run `npm run build` and confirm it succeeds
3. Tell me what changed and why
4. Tell me what you did not verify

Do not report a task complete without steps 1 and 2. If a check
fails, fix it or tell me — do not report success.

## Gotchas
- The dev server ignores changes to `astro.config.mjs`. Restart it.
- Cloudflare Pages is case-sensitive; local macOS is not. A
  wrong-case import works locally and 404s in production.
- The contact form posts to a Worker in `../worker/`, not this repo.
- Tests need a fresh build. Running against a stale `dist/` passes
  while testing the previous version.

## Ask before
- Adding any dependency
- Changing anything under `src/checkout/`
- Modifying build or deploy configuration
- Deleting files
- Any change touching pricing, legal, or policy text
- Anything that would change a published URL

Adapt the specifics; keep the shape.

Starting from /init

You do not have to write the first version by hand. Running /init in a project analyses the codebase and generates a starting CLAUDE.md.

What it produces is a reasonable inventory: the stack, the structure, the scripts it found. What it cannot produce is the prescriptive half — it does not know which of your patterns are deliberate and which are accidents nobody has cleaned up yet, and it has no way to know what people get wrong.

So treat the generated file as a first draft with a specific editing job attached:

  1. Delete the description. Anything that restates the directory tree or explains what a dependency is goes. This is usually half the file and it is the half with no value.
  2. Verify the commands. Generated command lists are read from configuration and are often right; they are also often stale, aspirational, or missing the one command that actually matters. Run each one.
  3. Add the prohibitions. There will be none. This is the section with the highest return and /init cannot write it, because prohibitions come from things that went wrong, not from things in the repository.
  4. Add the finishing procedure. Also absent. What must run before a task is complete, and what evidence you want.
  5. Add the gotchas. Every one of these is knowledge that exists only in someone’s head.

A useful way to get the raw material for steps three to five, in a session that has already worked in the repository for a while:

Based on what you have seen in this codebase, what would you have
done wrong if I had not corrected you? What conventions did you have
to infer rather than being told? What surprised you?

I want to add the answers to CLAUDE.md. Be specific and be honest
about what was genuinely ambiguous.

The answers tend to be more accurate than what you would have written from memory, because the ambiguities you have internalised are invisible to you and are exactly the ones that need writing down.

A real one: the theme behind this site

The template above is a generic web project. It is worth seeing what changes when the project is a specific platform, because the useful lines get much more specific.

This site is a Shopify theme, and its CLAUDE.md contains rules that would look strange out of context but that each exist because of something that actually went wrong:

## Do not
- Never push to the live theme. Push to the development theme and
  preview. The push script refuses the live role unless an explicit
  environment flag is set.
- Never write a credential into a Liquid template, a JS asset, or
  any file under theme/. Theme files are publicly served.
- Never replace the generated sitemap. Shopify maintains it.
- Never add review or rating structured data. We have no reviews;
  it would be false.

## Gotchas
- `policy` is not a Shopify template type. Neither templates/
  policy.json nor templates/policy.liquid will be used. Policy page
  styling has to branch inside layout/theme.liquid.
- Push order matters. Liquid assets, layouts, snippets, and sections
  must land before the section-group JSON that references them, or
  the push fails on an unknown section.
- The agents.md template renders in a restricted context. Only the
  `request` and `agents` objects exist — no `collections`, no
  `pages`, no `settings`. Anything else silently renders empty.

## Before finishing
1. Run tests/run-all.sh and show the output
2. Run theme check and confirm zero offences in authored files
3. Fetch the affected live URLs and report the actual status codes

None of that is inferable from the repository. The first gotcha in particular cost an hour of confusion — Shopify rejects templates/policy.json with an explicit error, and then silently ignores templates/policy.liquid, which reads like the fix worked until you load the page.

That is the shape of a mature CLAUDE.md. It is mostly a record of specific mistakes, written so they only have to happen once.

Splitting large files with imports

Claude Code supports importing other files into memory with an @path reference:

## Detailed standards

See @docs/code-style.md and @docs/accessibility.md

Imports resolve relative and absolute paths, and can nest a few levels deep. Used well, this keeps the root file scannable while letting genuinely long reference material live elsewhere.

Used badly, it is a way to pretend your file is short while loading 800 lines anyway. The dilution problem does not care which file the lines came from.

The distinction worth holding: import reference material you occasionally need, not rules that should always apply. A detailed style guide is a good import. Your list of prohibitions is not — it should be in the root file where it cannot be missed.

Monorepos and nested files

In a monorepo, put shared rules at the root and package-specific rules in each package:

CLAUDE.md                      # commit conventions, shared standards
packages/web/CLAUDE.md         # frontend rules
packages/api/CLAUDE.md         # backend rules
packages/shared/CLAUDE.md      # "changes here affect everything"

Rules load additively as work moves into a subtree, so a session working in packages/web/ gets the root rules plus the web rules and does not carry the API rules it will never use.

Two things to keep straight. Do not repeat root rules in child files — you now have two copies to maintain and they will disagree. And a shared package's file should state its blast radius explicitly, because that is exactly the context a session working only inside it lacks:

# packages/shared/CLAUDE.md

Everything here is consumed by web and api. A breaking change here
breaks both. Before changing any exported signature, find every
consumer and tell me what breaks.

Personal preferences vs. team rules

Keep the two apart. Anything that is genuinely about how you like to work — commit message style, how much explanation you want, tone — belongs in ~/.claude/CLAUDE.md, where it applies across all your projects and does not impose your habits on your colleagues.

The project file is for things that would be true regardless of who is sitting at the keyboard.

There is an older pattern of a CLAUDE.local.md for machine-specific settings; the current recommendation is to use imports instead, since a local file does not travel with worktrees. If you have machine-specific paths or credentials locations, import them from a gitignored file rather than maintaining a parallel memory file.

CLAUDE.md, settings, and hooks

Not everything you want enforced belongs in a Markdown file. Claude Code has three mechanisms and they differ in one important respect: whether compliance is guaranteed.

Mechanism What it is Enforcement
CLAUDE.md Instructions in context Followed, but not guaranteed
.claude/settings.json Permission rules and configuration Enforced by the tool
Hooks Shell commands on tool events Enforced by your own code

That distinction should drive where a rule goes.

"Never commit directly to main" in CLAUDE.md is an instruction that will usually be respected. As a permission rule in settings.json, it is a wall. If the consequence of the rule being missed is serious, put it where it cannot be missed — and keep the line in CLAUDE.md as well, so the reason is visible rather than just the refusal.

Hooks cover the third case: things that should happen every time regardless of whether anyone remembered. A formatter that runs after every edit does not need to be a rule anybody follows; it needs to be a hook. The general principle is that anything mechanical and unconditional is better as automation than as an instruction, and moving it out of CLAUDE.md also shortens the file, which helps everything left in it.

What genuinely belongs in CLAUDE.md is the judgement layer — the conventions, the reasons, the standards, the things that need a human-readable "why". Those cannot be automated, which is exactly why they need writing down.

How to write a rule that gets followed

The difference between a rule that changes behaviour and one that does not is usually specificity.

Weak Strong
Write clean code Functions under 40 lines; extract rather than nest past three levels
Be careful with the database Migrations are additive-only. Never drop a column in the same release that stops using it.
Follow accessibility best practices Every control has a visible associated label. A placeholder is not a label.
Test your changes Run npm run check and show me the output before reporting complete
Don't add unnecessary dependencies Ask before adding any dependency. Say what it costs and why the platform cannot do it.

Four properties make the right-hand column work: they are specific enough to have a clear boundary, checkable so compliance is observable, actionable in that they say what to do rather than what to value, and bounded so it is clear when they apply.

One more thing helps more than its length suggests: give a reason for the rules that look arbitrary.

- No client-side data fetching. This site is statically generated and
  served from the edge; a client fetch reintroduces the latency the
  architecture exists to remove.

A rule with a reason survives contact with an edge case, because the reason tells you whether the edge case is actually an exception. A bare prohibition either gets applied where it does not belong or gets abandoned at the first inconvenience.

Testing whether it works

A CLAUDE.md nobody has tested is a hypothesis. Three checks, each taking a couple of minutes.

1. Ask what it thinks the rules are

In a fresh session:

Without reading any other files: what are the rules for this project?
What should you never do? What do you run before saying a task is
complete? What should you ask me about first?

If the answers are vague where your file is specific, the file is too long, buried, or ambiguous. Anything missing from the answer is not currently working.

2. Give it a task that touches a rule

Ask for something small in an area with a prohibition attached. Then see whether the prohibition holds without you mentioning it. A rule that only works when you restate it is not doing its job.

3. Check the finishing procedure

Ask for any change at all and watch what happens at the end. Did it run the checks? Did it show you the output? Did it tell you what it could not verify? If not, that section needs to be more explicit or more prominent.

Rerun these after any significant edit to the file. Adding lines can push existing ones below the threshold where they get acted on — which is not intuitive, and is the reason the "just add another rule" instinct eventually backfires.

Keeping it true

The failure mode for a good CLAUDE.md is not neglect; it is drift. Commands get renamed, rules get relaxed, gotchas get fixed. A file that describes a project as it was eight months ago actively misleads every session, and it does so confidently.

Three habits keep it honest:

Update it when it fails. The moment you find yourself correcting the same thing twice, that correction belongs in the file. This is the primary source of good rules — not planning, but repeated annoyance.

Review it when the project changes shape. New framework, new deploy target, new team member — read the file top to bottom and delete what is no longer true. Deleting is most of the work.

Audit it quarterly. Takes five minutes:

Review CLAUDE.md against this repository.

For each statement, tell me whether it is still true. Check the
commands actually exist and run. Check the referenced paths exist.
Check the conventions still match what the code does.

Flag anything that is stale, contradicted by the code, or that a
linter now handles automatically and could be removed.

Then tell me what a new contributor would get wrong that the file
does not currently cover.

That last paragraph is the useful half. The gaps are harder to notice than the staleness, because nothing goes wrong visibly — you just quietly get worse output than you could have.

Anti-patterns

The everything file

Six hundred lines covering architecture, history, style, tutorials, and a glossary. Every rule competes with every other rule and none of them win. Cut to the prescriptive lines.

Documenting what the code says

A directory listing in Markdown. It cost you space, it will drift, and the code was already authoritative.

Restating the formatter

If Prettier fixes it, the file does not need an opinion about it.

Aspirational rules

"All code must have 100% test coverage" in a repository at 12%. Rules that are visibly not followed teach that rules here are decorative. Write the standard you actually hold.

Vague values

"Write maintainable, high-quality code." Nobody was planning otherwise. It occupies space that a real constraint could use.

Rules with no scope

"Always validate input" — everywhere? Client and server both? Say where.

Contradictions

"Move fast, ship quickly" three sections above "every change needs tests and review". One of these will be followed and you do not get to choose which.

Never testing it

Writing the file and assuming it works. It takes two minutes to check and the result is frequently surprising.

Where to go next

A CLAUDE.md is the persistent half of instructing an agent; the per-task half is the prompt. The prompt library covers that side, and the two are complementary — anything you find yourself repeating across prompts is a candidate for the file. To see where it fits in a whole build, read the complete website workflow. For platform-specific rules, the Shopify guide covers what a theme project's file should contain, and the SEO guide covers the standards worth encoding so they survive every future change.

Sources and further reading

More Claude Code guides