---
name: nextjs-coding-standards
description: Next.js 16 coding standards including file naming conventions, API patterns, theming, styling guidelines, and directory structure. Use when writing or reviewing code.
allowed-tools: Read, Grep, Glob
---

# Next.js 16 Coding Standards

Reference this skill when writing or reviewing code to ensure consistency with project conventions.

---

## File Naming Conventions

### Files and Directories

**Use kebab-case for all file names:**

```
✅ user-profile.tsx
✅ blog-post-card.tsx
✅ theme-toggle.tsx

❌ UserProfile.tsx
❌ blogPostCard.tsx
❌ ThemeToggle.tsx
```

**Why kebab-case?**

- Cross-platform compatibility (Windows vs Unix)
- URL-friendly (file names often map to routes)
- Easier to parse and read
- Industry standard for Next.js projects

**Special Next.js Files:**

```
page.tsx          # Route pages
layout.tsx        # Layout components
not-found.tsx     # 404 pages
loading.tsx       # Loading states
error.tsx         # Error boundaries
route.ts          # API route handlers
```

### Component Names (Inside Files)

**Use PascalCase for component names:**

```typescript
// File: user-profile.tsx
export function UserProfile() {
  return <div>...</div>
}

// File: blog-post-card.tsx
export default function BlogPostCard() {
  return <article>...</article>
}
```

### Variables, Functions, Props

**Use camelCase:**

```typescript
// Variables
const userSettings = {}
const isLoading = false

// Functions
function handleSubmit() {}
function formatDate(date: string) {}

// Props
interface ButtonProps {
  onClick: () => void
  isDisabled: boolean
  className?: string
}

// Custom Hooks
function useTheme() {}
function useMarkdown() {}
```

### Constants

**Use SCREAMING_SNAKE_CASE:**

```typescript
const API_BASE_URL = 'https://api.example.com'
const MAX_RETRIES = 3
const DEFAULT_LOCALE = 'ro-RO'
```

---

## Next.js 16 API Patterns

### Route Handlers

**Use Route Handlers (not legacy API Routes):**

```typescript
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'

// Export named functions for HTTP methods
export async function GET(request: NextRequest) {
  const posts = await getAllPosts()
  return NextResponse.json({ posts })
}

export async function POST(request: NextRequest) {
  const body = await request.json()
  // Process request
  return NextResponse.json({ success: true }, { status: 201 })
}
```

### Type-Safe Validation with Zod

**Always validate input with Zod:**

```typescript
import { z } from 'zod'
import { NextRequest, NextResponse } from 'next/server'

const bodySchema = z.object({
  title: z.string().min(1).max(200),
  content: z.string(),
  tags: z.array(z.string()).max(3),
})

export async function POST(request: NextRequest) {
  const json = await request.json()
  const parsed = bodySchema.safeParse(json)

  if (!parsed.success) {
    return NextResponse.json({ error: 'Validation failed', details: parsed.error }, { status: 400 })
  }

  // parsed.data is fully typed
  const { title, content, tags } = parsed.data
  // ... business logic
}
```

**Key Points:**

- Use `safeParse()` instead of `parse()` to avoid try/catch
- Return structured error responses
- Use appropriate HTTP status codes
- Infer TypeScript types from Zod schemas

### Error Handling

**Return meaningful status codes:**

```typescript
200 // Success
201 // Created
400 // Bad Request (validation errors)
401 // Unauthorized
403 // Forbidden
404 // Not Found
500 // Internal Server Error
```

**Structured error responses:**

```typescript
return NextResponse.json(
  {
    error: 'Resource not found',
    code: 'NOT_FOUND',
    timestamp: new Date().toISOString(),
  },
  { status: 404 }
)
```

---

## Theming Patterns

### next-themes Setup

**Root Layout (Server Component):**

```typescript
// app/layout.tsx
import { ThemeProvider } from 'next-themes'

export default function RootLayout({ children }) {
  return (
    <html lang="ro" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="dark"
          enableSystem={false}
          storageKey="blog-theme"
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}
```

**Critical:** Always add `suppressHydrationWarning` to `<html>` tag.

### Theme Toggle Component

**Avoid hydration mismatches with mounted state:**

```typescript
// components/theme-toggle.tsx
'use client'

import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'

export function ThemeToggle() {
  const { theme, setTheme } = useTheme()
  const [mounted, setMounted] = useState(false)

  // Prevent hydration mismatch
  useEffect(() => setMounted(true), [])

  if (!mounted) {
    return <div className="w-9 h-9 animate-pulse" />
  }

  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      {theme === 'dark' ? '🌙' : '☀️'}
    </button>
  )
}
```

**Pattern:** Always check `mounted` state before rendering theme-dependent UI.

### CSS Variables for Theme Tokens

**Define in globals.css:**

```css
@layer base {
  :root {
    --bg-primary: 255 255 255;
    --bg-secondary: 248 250 252;
    --text-primary: 15 23 42;
    --text-secondary: 51 65 85;
  }

  .dark {
    --bg-primary: 24 24 27;
    --bg-secondary: 15 23 42;
    --text-primary: 241 245 249;
    --text-secondary: 203 213 225;
  }
}
```

**Use in components:**

```tsx
<div className="bg-[rgb(var(--bg-primary))] text-[rgb(var(--text-primary))]">
  Theme-aware component
</div>
```

### Tailwind Configuration

**Enable class-based dark mode:**

```javascript
// tailwind.config.js
module.exports = {
  darkMode: 'class', // Required for next-themes
  theme: {
    extend: {
      colors: {
        'dark-primary': '#18181b',
        accent: {
          DEFAULT: '#164e63',
          hover: '#155e75',
        },
      },
    },
  },
}
```

**Use dark: variant:**

```tsx
<div className="bg-slate-100 dark:bg-zinc-900 text-slate-900 dark:text-slate-100">
  Automatically switches based on theme
</div>
```

---

## Styling Guidelines (Tailwind CSS)

### Utility-First Philosophy

**Prefer inline utilities over custom CSS:**

```tsx
// ✅ Good: Inline utilities
<article className="border-4 border-slate-800 p-6 bg-zinc-900 hover:border-cyan-900">
  <h2 className="text-2xl font-bold uppercase tracking-wider">Title</h2>
</article>

// ❌ Avoid: @apply (increases bundle size)
/* styles.css */
.card {
  @apply border-4 border-slate-800 p-6 bg-zinc-900;
}
```

**Exception:** Only use `@apply` for truly global base styles in `globals.css`.

### Component Extraction

**Extract to React components when reused:**

```typescript
// ✅ Extract repeated patterns to components
export function Card({ children, className = "" }) {
  return (
    <div className={`border-4 border-slate-800 p-6 bg-zinc-900 ${className}`}>
      {children}
    </div>
  )
}

// Usage
<Card className="hover:border-cyan-900">
  <h2>Title</h2>
</Card>
```

**Don't extract:** One-off components or single-use patterns.

### Class Organization

**Group utilities logically:**

```tsx
// Layout → Spacing → Colors → Typography → Effects
<div className="
  flex flex-col           // Layout
  gap-4 p-6              // Spacing
  bg-zinc-900            // Colors
  border-4 border-slate-800
  text-slate-100
  font-mono text-sm      // Typography
  uppercase tracking-wider
  hover:border-cyan-900  // Effects
  transition-colors
">
```

**Tip:** Use Prettier plugin for automatic Tailwind class sorting.

### Responsive Design

**Mobile-first approach:**

```tsx
// Base classes = mobile, add breakpoints for larger screens
<div className="
  flex-col              // Mobile: vertical stack
  md:flex-row           // Tablet+: horizontal layout
  lg:gap-8              // Desktop: more spacing
">
```

**Standard breakpoints:**

```
sm: 640px   // Small tablets
md: 768px   // Tablets
lg: 1024px  // Laptops
xl: 1280px  // Desktops
2xl: 1536px // Large screens
```

### Conditional Styling

**For complex conditions, use variants:**

```typescript
const cardVariants = {
  default: "border-slate-800 bg-zinc-900",
  highlighted: "border-cyan-600 bg-cyan-950",
  error: "border-red-600 bg-red-950",
}

<Card className={cardVariants[variant]} />
```

---

## Directory Structure Standards

### Project Organization

```
app/
├── (auth)/              # Route groups (no URL segment)
│   ├── login/
│   └── register/
├── api/                 # API routes
│   └── posts/
│       └── route.ts
├── blog/
│   ├── page.tsx
│   └── [slug]/
│       └── page.tsx
├── @breadcrumbs/        # Parallel routes
│   └── default.tsx
├── layout.tsx           # Root layout
├── globals.css
└── page.tsx

components/
├── blog/                # Domain-specific components
│   ├── post-card.tsx
│   └── markdown-renderer.tsx
├── layout/              # Layout components
│   ├── header.tsx
│   └── footer.tsx
└── ui/                  # Reusable UI primitives
    ├── button.tsx
    └── card.tsx

lib/
├── api/                 # API clients, fetch wrappers
│   └── client.ts
├── types/               # TypeScript type definitions
│   └── post.ts
├── markdown.ts          # Business logic modules
├── seo.ts
└── utils.ts             # Pure utility functions

public/
├── blog/                # Blog-specific assets
│   └── images/
└── icons/

content/                 # Content files (outside app/)
└── blog/
    └── posts.md
```

### lib/ Organization

**Modules in lib/:**

- Substantial business logic (markdown.ts, seo.ts)
- API clients and data fetching
- Database connections
- Authentication logic

**Utils in lib/utils.ts:**

- Pure helper functions
- Formatters (formatDate, formatCurrency)
- Validators (isEmail, isValidUrl)
- String manipulations

**Types in lib/types/:**

- Shared TypeScript interfaces
- API response types
- Domain models
- Colocated with their modules when possible

### Component Organization

**By domain/feature:**

```
components/
├── blog/               # Blog-specific
│   ├── post-card.tsx
│   ├── post-list.tsx
│   └── markdown-renderer.tsx
├── auth/               # Auth-specific
│   ├── login-form.tsx
│   └── signup-form.tsx
└── ui/                 # Reusable primitives
    ├── button.tsx
    ├── card.tsx
    └── input.tsx
```

**Not by type:**

```
❌ Don't organize like this:
components/
├── forms/
├── buttons/
├── cards/
└── modals/
```

### Public Assets

**Organize by feature:**

```
public/
├── blog/
│   ├── images/
│   └── thumbnails/
├── icons/
│   ├── social/
│   └── ui/
└── fonts/
```

**Naming conventions:**

- Use descriptive names: `hero-background.jpg` not `img1.jpg`
- Use kebab-case: `user-avatar.png`
- Include dimensions for images: `logo-512x512.png`

---

## TypeScript Best Practices

### Type Safety

**Avoid `any`:**

```typescript
// ❌ Bad
function processData(data: any) {}

// ✅ Good
function processData(data: unknown) {
  if (typeof data === 'string') {
    // TypeScript knows data is string here
  }
}

// ✅ Better: Use specific types
interface PostData {
  title: string
  content: string
}
function processData(data: PostData) {}
```

### Infer Types from Zod

**Don't duplicate types:**

```typescript
import { z } from 'zod'

// Define schema once
const postSchema = z.object({
  title: z.string(),
  content: z.string(),
  tags: z.array(z.string()),
})

// Infer TypeScript type
type Post = z.infer<typeof postSchema>

// Now you have both runtime validation and compile-time types
```

### Type Imports

**Use type imports for types only:**

```typescript
// ✅ Good: Explicit type import
import type { Metadata } from 'next'
import type { Post } from '@/lib/types/post'

// ✅ Mixed: Regular and type imports
import { getAllPosts } from '@/lib/markdown'
import type { Post } from '@/lib/types/post'
```

---

## Next.js 16 Specific Patterns

### Async Server Components

**Fetch data directly in components:**

```typescript
// app/blog/page.tsx
export default async function BlogPage() {
  // Server-side data fetching (no useEffect needed)
  const posts = await getAllPosts()

  return (
    <div>
      {posts.map(post => (
        <PostCard key={post.slug} post={post} />
      ))}
    </div>
  )
}
```

### Static Generation

**Use generateStaticParams for dynamic routes:**

```typescript
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts()
  return posts.map(post => ({
    slug: post.slug.split('/'), // For catch-all routes
  }))
}

export async function generateMetadata({ params }) {
  const post = getPostBySlug(params.slug.join('/'))
  return {
    title: post.frontmatter.title,
    description: post.frontmatter.description,
  }
}

export default async function PostPage({ params }) {
  const post = getPostBySlug(params.slug.join('/'))
  return <article>{/* render post */}</article>
}
```

### Client Components

**Minimize 'use client' usage:**

```typescript
// ❌ Unnecessary client component
'use client'
export function StaticCard({ title }) {
  return <div>{title}</div>
}

// ✅ Keep as server component (default)
export function StaticCard({ title }) {
  return <div>{title}</div>
}

// ✅ Only use 'use client' when necessary
'use client'
import { useState } from 'react'

export function InteractiveCard({ title }) {
  const [isOpen, setIsOpen] = useState(false)
  return (
    <div onClick={() => setIsOpen(!isOpen)}>
      {title}
    </div>
  )
}
```

**When to use 'use client':**

- Using React hooks (useState, useEffect, etc.)
- Using event handlers (onClick, onChange, etc.)
- Using browser APIs (window, localStorage, etc.)
- Using context consumers

### Parallel Routes

**Use for layout composition:**

```typescript
// app/layout.tsx
export default function RootLayout({
  children,
  breadcrumbs,  // From @breadcrumbs parallel route
}: {
  children: React.ReactNode
  breadcrumbs: React.ReactNode
}) {
  return (
    <>
      {breadcrumbs}
      <main>{children}</main>
    </>
  )
}
```

---

## Common Pitfalls

### 1. Hydration Mismatches

**Problem:** Theme-dependent content renders differently on server vs client.

**Solution:** Use mounted state pattern (see Theming section).

### 2. Image Paths

**Problem:** Incorrect public asset paths.

```typescript
// ❌ Wrong
<Image src="blog/image.jpg" />

// ✅ Correct
<Image src="/blog/image.jpg" />  // Leading slash
```

### 3. Dynamic Route Params

**Problem:** Forgetting slug should be an array for catch-all routes.

```typescript
// app/blog/[...slug]/page.tsx
export async function generateStaticParams() {
  // ❌ Wrong
  return posts.map(post => ({ slug: post.slug }))

  // ✅ Correct
  return posts.map(post => ({ slug: post.slug.split('/') }))
}
```

### 4. Over-using Client Components

**Problem:** Adding 'use client' unnecessarily.

**Solution:** Keep components as Server Components by default. Only add 'use client' when you need hooks, events, or browser APIs.

### 5. Date Formats

**Problem:** Inconsistent date formatting.

**Solution:** Use consistent ISO format (YYYY-MM-DD) in data, format for display:

```typescript
// In frontmatter
date: '2025-01-15'

// For display
formatDate(post.frontmatter.date) // "15 ianuarie 2025" (Romanian)
```

---

## Reference

For project-specific architecture and design philosophy, see `CLAUDE.md` at the root of this repository.

This skill focuses on coding conventions and standards. For architecture patterns, markdown processing, and industrial design aesthetic guidelines, refer to the main documentation.
