# New Feature Nextjs

> Pre-flight checklist for adding a new feature to a Next.js 15 + TypeScript app. Load when creating a new page, route, server action, or feature module. Forces feature-driven colocation, RSC-first thinking, and the typed cache-tag pattern from line one — prevents the most common AI failures (1500-line page.tsx, 'use client' on the layout, scattered revalidateTag magic strings).

- **Type:** Skill
- **Install:** `agentstack add skill-yerdaulet-damir-vibe-coding-rules-new-feature-nextjs`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [yerdaulet-damir](https://agentstack.voostack.com/s/yerdaulet-damir)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [yerdaulet-damir](https://github.com/yerdaulet-damir)
- **Source:** https://github.com/yerdaulet-damir/vibe-coding-rules/tree/main/.claude/skills/new-feature-nextjs
- **Website:** https://vibecodex-omega.vercel.app

## Install

```sh
agentstack add skill-yerdaulet-damir-vibe-coding-rules-new-feature-nextjs
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# new-feature-nextjs

Do not open any file for editing until all 6 steps are completed.

---

## Step 1 — Define the feature surface

Write the surface in plain text before any code:

```
Feature name: users
Routes: /users (list), /users/[id] (detail)
Mutations: createUser, updateUser, deleteUser
Reads: getUser(id), listUsers()
Cache tags: user(id), userList(), userOrders(userId)
```

If you can't fill all 5 lines in 2 sentences each, the scope is unclear — clarify with the user first.

---

## Step 2 — Pick the folder location (Principle C1)

| Scope | Location |
|-------|----------|
| Reusable everywhere (Button, Input) | `src/components/ui/` |
| Belongs to one business domain | `src/features//` |
| Pure helper used in 2+ features | `src/lib//` |
| Used in exactly one place | colocate next to that place (Principle C6) |

For a new feature, default to `src/features//` with this structure:

```
src/features//
├── components/         # JSX (server + client)
├── actions.ts          # Server Actions
├── repository.ts       # Drizzle adapter (only file importing drizzle-orm)
├── protocols.ts        # Repository protocol/interface
├── schema.ts           # Zod schemas
└── store.ts            # Zustand (only if genuinely needed)
```

---

## Step 3 — Add cache tags FIRST (Principle D1)

Before writing any Server Action, add the tags to `src/lib/cache/tags.ts`:

```typescript
// src/lib/cache/tags.ts
export const tags = {
  // ... existing tags
  user: (id: string) => `user:${id}` as const,
  userList: () => 'user:list' as const,
} as const;
```

**Rule: zero `revalidateTag('magic-string')` in the new feature.** Every invalidation goes through the typed DSL. Typos must be compile errors.

---

## Step 4 — Build bottom-up (parallel to FastAPI new-feature skill)

```
1. Schema (Zod)         → src/features//schema.ts
2. Repository protocol  → src/features//protocols.ts
3. Repository impl      → src/features//repository.ts (Drizzle)
4. Server Actions       → src/features//actions.ts (uses tags + repo via Protocol)
5. Components (server)  → src/features//components/Page.tsx
6. Components (client)  → src/features//components/.tsx ('use client')
7. Route               → src/app//page.tsx (thin, /page.tsx` stays under 20 lines and just imports `Page` (Principle C2)

---

## Step 5 — Wire Suspense + PPR (Principles D3, D4)

Streaming setup in the page:

```tsx
// src/app/users/page.tsx
import { UsersPage } from '@/features/users/components/UsersPage';
import { usersRepo } from '@/features/users/repository';

export const experimental_ppr = true;  // D4

export default function Page() {
  const usersPromise = usersRepo.list(50);  // not awaited
  return ;  // streamed via Suspense
}
```

Server feature page wraps async children in ``:

```tsx
// src/features/users/components/UsersPage.tsx
import { Suspense } from 'react';
import { UserList } from './UserList';

export function UsersPage({ usersPromise }: Props) {
  return (
    }>
      
    
  );
}
```

Client leaf unwraps with `use()`:

```tsx
'use client';
import { use } from 'react';

export function UserList({ usersPromise }: Props) {
  const users = use(usersPromise);
  return ...;
}
```

---

## Step 6 — Verify principles before committing

Run these grep checks:

```bash
# C2: page is thin (/page.tsx

# C3: file size cap
find src/features/ -name "*.ts*" | xargs wc -l | sort -rn | head

# C4: no 'use client' on pages or layouts
grep -rn "'use client'" src/app/ | grep -E "(page|layout)\.tsx"

# C5: errors are values, not throws (in Server Actions)
grep -n "throw new" src/features//actions.ts

# C8: no `any` and no @ts-ignore
grep -rn ": any\b\| as any\b\|@ts-ignore" src/features//

# C10: no inline styles
grep -rn "style=\{\{" src/features//

# D1: every revalidateTag goes through tags.X()
grep -n "revalidateTag(['\"]" src/features//  # must be empty

# D6: drizzle-orm imported only from repository.ts
grep -rn "from 'drizzle-orm" src/features//  # only repository.ts allowed
```

| Check | Expected result |
|-------|----------------|
| `page.tsx` line count |  200 LOC | refactor before commit |
| `'use client'` on page/layout | zero hits |
| `throw new` in actions.ts | zero hits (return errors as values) |
| `any` / `@ts-ignore` | zero hits |
| `style={{}}` | zero hits |
| `revalidateTag('...')` magic string | zero hits |
| `drizzle-orm` outside repository.ts | zero hits |

```bash
pnpm typecheck
pnpm lint
```

Both must pass.

---

## Verification checklist

- [ ] Surface defined in writing before any code
- [ ] Folder location follows feature-driven colocation
- [ ] Cache tags added to `lib/cache/tags.ts` before Server Actions
- [ ] Build order followed: schema → protocol → repo → action → component → route
- [ ] Server fetch passes Promise down; client unwraps with `use()`
- [ ] PPR enabled if route has static + dynamic mix
- [ ] All 8 grep checks pass
- [ ] `pnpm typecheck && pnpm lint` exits 0

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [yerdaulet-damir](https://github.com/yerdaulet-damir)
- **Source:** [yerdaulet-damir/vibe-coding-rules](https://github.com/yerdaulet-damir/vibe-coding-rules)
- **License:** MIT
- **Homepage:** https://vibecodex-omega.vercel.app

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-yerdaulet-damir-vibe-coding-rules-new-feature-nextjs
- Seller: https://agentstack.voostack.com/s/yerdaulet-damir
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
