Files
2025-11-12 16:17:55 +02:00

759 lines
16 KiB
Markdown

---
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.