Install
$ agentstack add skill-yerdaulet-damir-vibe-coding-rules-new-feature-nextjs ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
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:
// 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 ``:
// 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():
'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:
# 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 |
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.tsbefore 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 lintexits 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
- Source: yerdaulet-damir/vibe-coding-rules
- License: MIT
- Homepage: https://vibecodex-omega.vercel.app
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.