> ## Documentation Index
> Fetch the complete documentation index at: https://tmbv.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Six rules for an AI-legible Next.js codebase

> A short operating manual for structuring projects so coding agents can actually help.

export const FramedYouTube = ({src, title}) => {
  const getYouTubeId = url => {
    if (!url) return null;
    const embedMatch = url.match(/youtube\.com\/embed\/([^?&]+)/);
    if (embedMatch) return embedMatch[1];
    const watchMatch = url.match(/youtube\.com\/watch\?v=([^&]+)/);
    if (watchMatch) return watchMatch[1];
    const shortMatch = url.match(/youtu\.be\/([^?&]+)/);
    if (shortMatch) return shortMatch[1];
    return null;
  };
  const videoId = getYouTubeId(src);
  const embedUrl = videoId ? `https://www.youtube.com/embed/${videoId}?modestbranding=1&rel=0&showinfo=0` : src;
  return <Frame>
			<iframe className="w-full aspect-video rounded-xl" src={embedUrl} title={title} aria-label={title} allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe>
		</Frame>;
};

export const FramedImage = ({src, alt, ariaLabel, citationText, citationUrl}) => {
  const caption = citationText ? citationUrl ? <a href={citationUrl} target="_blank" rel="noopener noreferrer">
				{citationText}
			</a> : citationText : undefined;
  return <Frame caption={caption}>
			<img src={src} alt={alt} aria-label={ariaLabel || alt} />
		</Frame>;
};

Coding agents are only as good as the context you hand them. After a year of shipping Next.js apps with Cursor and Claude Code, the structure that keeps producing clean diffs looks nothing like the default `create-next-app` layout.

Six rules. Each one earns its place.

<FramedImage citationText="@samdape" citationUrl="https://x.com/samdape" src="https://ik.imagekit.io/pqnkhqkwi/tmbv/feature%20first%20structure/G3I7-gYWoAA-l5z.jpeg?updatedAt=1760455509890" ariaLabel="Create folder" alt="Create folder" />

### 1. Organize by feature, not by file type

A feature is the unit of work an agent reasons about. "Add avatar upload to profile" touches a component, a router, a schema, a hook, a store. If those five files live under five different top-level folders, you spend the first three minutes of every task assembling a context package by hand.

Put them together. Stop paying the tax.

### 2. The folder is the prompt

```diff Project structure theme={null}
- src/components/profile-avatar.tsx
- src/components/profile-form.tsx
- src/hooks/use-profile-data.ts
- src/trpc/routers/profile.ts
- src/zustand/profile-store.ts
+ src/features/profile/
+   api/         # trpc router
+   components/  # Avatar, ProfileForm
+   hooks/       # useProfileData
+   lib/         # profile-only utils
+   validation/  # zod schemas
+   store.ts     # zustand
```

With the left layout, your prompt starts with "find all files related to profile." With the right layout, your prompt starts with `@src/features/profile`. That's the whole argument.

### 3. `components/` is for primitives and chrome only

`src/components/ui/` holds shadcn. `src/components/global/` holds the Navbar and Footer. Nothing else goes here. If a component is used by exactly one feature, it lives inside that feature.

This rule is the one people break first. Hold the line — the moment you let `ProfileHeader.tsx` slip into `components/`, the pattern starts rotting.

### 4. Shared code has to earn its spot

`src/lib/`, `src/hooks/`, the top-level `trpc/` wiring — these are for things used by two or more features. Everything starts inside a feature folder and graduates out only when a second consumer appears. YAGNI, applied to directories.

### 5. Route handlers stay thin

`src/app/` stays as lean as Next.js will let it — route files, layouts, maybe a `loading.tsx`. The actual work is imported from `features/*`. Routes become a table of contents; the chapters live elsewhere.

### 6. Deletion should be a one-liner

The real test of a good structure: can you kill a feature with `rm -rf src/features/profile` and one search-and-replace for imports? If yes, your boundaries are real. If no, you have leaks to fix.

<FramedYouTube src="https://www.youtube.com/watch?v=nHpO_Vh2b_Y" title="Feature Driven Development" />

### Try it on one feature

Don't refactor the whole app on a Tuesday afternoon. Pick the feature you touched most this week, carve out `src/features/<it>/`, and move its files in. Run the app. Open your agent. Point it at the folder.

You'll know within one prompt whether to keep going.
