active

TypeScript Next.js Rules

Safe

Context rules for AI agents working in TypeScript/Next.js App Router projects. Covers strict typing, server vs client components, Tailwind, Zod, and file conventions.

@atapifire/typescript-next-js-rules

typescript
nextjs
react
rules
context
tailwind

TypeScript Next.js Rules

Context skill: Install this into your agent to enforce consistent, production-quality patterns in TypeScript Next.js App Router projects. Works with Claude Code, Cursor, Windsurf, and any agent that reads context files.


Stack Assumptions

  • Next.js 14+ with App Router (not Pages Router)
  • TypeScript with strict mode enabled ("strict": true in tsconfig)
  • Tailwind CSS for styling
  • Zod for runtime validation
  • Supabase or Prisma for database (adapt as needed)

File & Directory Conventions

code
app/
  (group)/          ← Route groups (no URL segment)
    layout.tsx      ← Shared layout
    page.tsx        ← Route page (server component by default)
    loading.tsx     ← Suspense fallback
    error.tsx       ← Error boundary ('use client' required)
  api/
    route.ts        ← API routes (GET, POST, etc. as named exports)
components/
  ui/               ← Primitive UI: Button, Card, Badge, etc.
  feature/          ← Feature-specific composed components
lib/
  utils/            ← Pure utility functions (no React)
  types/            ← TypeScript type definitions
  actions/          ← Server Actions
hooks/              ← Custom React hooks (client-side)

Server vs Client Components

Default to Server Components. Only add 'use client' when you need:

  • useState or useReducer
  • useEffect or lifecycle hooks
  • Browser APIs (window, localStorage, etc.)
  • Event handlers that run in the browser
  • Third-party client-only libraries

Never put 'use client' at the top of a layout file.


TypeScript Rules

typescript
// ✅ Always type function return values explicitly
async function getUser(id: string): Promise<User | null> { ... }

// ✅ Use `type` for unions/intersections, `interface` for object shapes
type Status = 'pending' | 'active' | 'archived'
interface UserProfile { id: string; email: string; }

// ✅ Never use `any` — use `unknown` and narrow
function parse(input: unknown): string {
  if (typeof input !== 'string') throw new Error('Expected string')
  return input
}

// ❌ Never
const data: any = response.json()

// ✅ Type API responses with Zod
const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email() })
type User = z.infer<typeof UserSchema>

API Routes

typescript
// app/api/users/route.ts
import { NextResponse } from 'next/server'
import { z } from 'zod'

const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
})

export async function POST(request: Request) {
  const body = await request.json()
  const result = CreateUserSchema.safeParse(body)
  if (!result.success) {
    return NextResponse.json({ error: result.error.flatten() }, { status: 422 })
  }
  // ... proceed with result.data (fully typed)
}

Data Fetching

typescript
// ✅ Server Component data fetching — no useEffect
export default async function UsersPage() {
  const users = await getUsers() // Direct async call in component
  return <UserList users={users} />
}

// ✅ Parallel fetching with Promise.all
const [user, posts] = await Promise.all([getUser(id), getPosts(id)])

// ✅ Use `cache` for request deduplication
import { cache } from 'react'
const getUser = cache(async (id: string) => { ... })

Error Handling

typescript
// ✅ Never throw in Server Components — use error.tsx
// app/(main)/error.tsx
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return <div><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>
}

// ✅ API routes always return typed error shapes
return NextResponse.json({ error: 'Not found', code: 'USER_NOT_FOUND' }, { status: 404 })

Tailwind CSS

  • Use Tailwind utility classes directly — no custom CSS unless absolutely necessary
  • Use cn() helper (from clsx + tailwind-merge) to conditionally join classes
  • Dark mode via dark: variants (not separate stylesheets)
  • Never use inline style={{}} for values that Tailwind covers
  • Use @apply sparingly — only in component-level .module.css files if needed

Do Not

  • Do not use pages/ directory — App Router only
  • Do not import server-only code into client components (will cause runtime errors)
  • Do not use getServerSideProps or getStaticProps — use async Server Components
  • Do not console.log in production code — use a proper logger
  • Do not hardcode environment variables — use process.env.VAR with validation on startup
  • Do not use default exports for utility functions — named exports only

Playground

<!DOCTYPE html><html><head><meta charset='utf-8'><style>*{box-sizing:border-box;margin:0;padding:0}body{background:#0d1117;font-family:monospace;font-size:11px;height:100vh;display:flex;flex-direction:column;overflow:hidden;padding:12px}.title{color:#58a6ff;font-size:13px;font-weight:bold;margin-bottom:6px}.subtitle{color:#8b949e;font-size:10px;margin-bottom:10px}.rule{display:flex;align-items:flex-start;gap:8px;margin-bottom:7px;line-height:1.5}.dot{color:#3fb950;font-size:14px;line-height:1.3;flex-shrink:0}.text{color:#e6edf3}.hl{color:#3fb950}.section{color:#e3b341;font-size:10px;text-transform:uppercase;letter-spacing:.08em;margin:10px 0 5px}</style></head><body><div class='title'>TypeScript · Next.js Rules</div><div class='subtitle'>Active in every session — enforced automatically</div><div class='section'>Types</div><div class='rule'><span class='dot'>●</span><div class='text'>No <span class='hl'>any</span> — use <span class='hl'>unknown</span> with narrowing or explicit generics</div></div><div class='rule'><span class='dot'>●</span><div class='text'>Prefer <span class='hl'>interface</span> for object shapes, <span class='hl'>type</span> for unions/aliases</div></div><div class='rule'><span class='dot'>●</span><div class='text'>All <span class='hl'>async</span> functions return <span class='hl'>Promise&lt;T&gt;</span> — never implicit any</div></div><div class='section'>Next.js</div><div class='rule'><span class='dot'>●</span><div class='text'>Server Components by default — add <span class='hl'>'use client'</span> only when needed</div></div><div class='rule'><span class='dot'>●</span><div class='text'>Data fetching in Server Components via <span class='hl'>fetch()</span> with cache options</div></div><div class='rule'><span class='dot'>●</span><div class='text'>API routes: validate input with <span class='hl'>zod</span> before touching the DB</div></div><div class='section'>Style</div><div class='rule'><span class='dot'>●</span><div class='text'>Tailwind only — no inline styles, no CSS modules</div></div><div class='rule'><span class='dot'>●</span><div class='text'>Imports: external → internal → relative (enforced by ESLint)</div></div></body></html>
Dormant$0/mo

$20 more to next tier

Info

Created February 20, 2026
Version 1.0.0
Context
Terminal output

Embed

Add this skill card to any webpage.

<iframe src="https://skillslap.com/skill/273175f6-117a-4657-b0f7-5c963c0bbaeb/embed"
        width="400" height="200"
        style="border:none;border-radius:12px;"
        title="SkillSlap Skill: TypeScript Next.js Rules">
</iframe>