agentsclimarketplace

New feature nextjs

Skill yerdaulet-damir/vibe-coding-rules/.claude/skills/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).From its SKILL.md

Install
npx -y skills add yerdaulet-damir/vibe-coding-rules --skill new-feature-nextjs

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 8 commands, including `wc -l src/app/<route>/page.tsx` and 7 more.

SKILL.md

6.1 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it

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)

ScopeLocation
Reusable everywhere (Button, Input)src/components/ui/
Belongs to one business domainsrc/features/<domain>/
Pure helper used in 2+ featuressrc/lib/<area>/
Used in exactly one placecolocate next to that place (Principle C6)

For a new feature, default to src/features/<domain>/ with this structure:

src/features/<domain>/
├── 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/<domain>/schema.ts
2. Repository protocol  → src/features/<domain>/protocols.ts
3. Repository impl      → src/features/<domain>/repository.ts (Drizzle)
4. Server Actions       → src/features/<domain>/actions.ts (uses tags + repo via Protocol)
5. Components (server)  → src/features/<domain>/components/<Feature>Page.tsx
6. Components (client)  → src/features/<domain>/components/<Leaf>.tsx ('use client')
7. Route               → src/app/<route>/page.tsx (thin, < 20 lines)

Critical rules during build:

  • actions.ts imports usersRepo typed as UsersRepoProtocol — never imports drizzle-orm directly
  • Server Components fetch data and pass Promises down (Principle D3) — don't await and pass values unless you must
  • 'use client' goes on the leaf component that needs useState / onClick / use() — never on the page or layout (Principle C4)
  • app/<route>/page.tsx stays under 20 lines and just imports <Feature>Page (Principle C2)

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

Streaming setup in the page:

// 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 <UsersPage usersPromise={usersPromise} />;  // streamed via Suspense
}

Server feature page wraps async children in <Suspense>:

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

export function UsersPage({ usersPromise }: Props) {
  return (
    <Suspense fallback={<UserListSkeleton />}>
      <UserList usersPromise={usersPromise} />
    </Suspense>
  );
}

Client leaf unwraps with use():

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

export function UserList({ usersPromise }: Props) {
  const users = use(usersPromise);
  return <ul>...</ul>;
}

Step 6 — Verify principles before committing

Run these grep checks:

# C2: page is thin (< 20 lines)
wc -l src/app/<route>/page.tsx

# C3: file size cap
find src/features/<domain> -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/<domain>/actions.ts

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

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

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

# D6: drizzle-orm imported only from repository.ts
grep -rn "from 'drizzle-orm" src/features/<domain>/  # only repository.ts allowed
CheckExpected result
page.tsx line count< 20
Any file > 200 LOCrefactor before commit
'use client' on page/layoutzero hits
throw new in actions.tszero hits (return errors as values)
any / @ts-ignorezero hits
style={{}}zero hits
revalidateTag('...') magic stringzero hits
drizzle-orm outside repository.tszero 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.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

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.