--- 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
...
} // File: blog-post-card.tsx export default function BlogPostCard() { return
...
} ``` ### 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 ( {children} ) } ``` **Critical:** Always add `suppressHydrationWarning` to `` 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
} return ( ) } ``` **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
Theme-aware component
``` ### 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
Automatically switches based on theme
``` --- ## Styling Guidelines (Tailwind CSS) ### Utility-First Philosophy **Prefer inline utilities over custom CSS:** ```tsx // ✅ Good: Inline utilities

Title

// ❌ 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 (
{children}
) } // Usage

Title

``` **Don't extract:** One-off components or single-use patterns. ### Class Organization **Group utilities logically:** ```tsx // Layout → Spacing → Colors → Typography → Effects
``` **Tip:** Use Prettier plugin for automatic Tailwind class sorting. ### Responsive Design **Mobile-first approach:** ```tsx // Base classes = mobile, add breakpoints for larger screens
``` **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", } ``` --- ## 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 // 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 (
{posts.map(post => ( ))}
) } ``` ### 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
{/* render post */}
} ``` ### Client Components **Minimize 'use client' usage:** ```typescript // ❌ Unnecessary client component 'use client' export function StaticCard({ title }) { return
{title}
} // ✅ Keep as server component (default) export function StaticCard({ title }) { return
{title}
} // ✅ Only use 'use client' when necessary 'use client' import { useState } from 'react' export function InteractiveCard({ title }) { const [isOpen, setIsOpen] = useState(false) return (
setIsOpen(!isOpen)}> {title}
) } ``` **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}
{children}
) } ``` --- ## 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 // ✅ Correct // 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.