---
name: next-best-practices
description: Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
user-invocable: false
---
# Next.js Best Practices
Apply these rules when writing or reviewing Next.js code.
## File Conventions
See [file-conventions.md](./file-conventions.md) for:
- Project structure and special files
- Route segments (dynamic, catch-all, groups)
- Parallel and intercepting routes
- Middleware rename in v16 (middleware → proxy)
## RSC Boundaries
Detect invalid React Server Component patterns.
See [rsc-boundaries.md](./rsc-boundaries.md) for:
- Async client component detection (invalid)
- Non-serializable props detection
- Server Action exceptions
## Async Patterns
Next.js 15+ async API changes.
See [async-patterns.md](./async-patterns.md) for:
- Async `params` and `searchParams`
- Async `cookies()` and `headers()`
- Migration codemod
## Runtime Selection
See [runtime-selection.md](./runtime-selection.md) for:
- Default to Node.js runtime
- When Edge runtime is appropriate
## Directives
See [directives.md](./directives.md) for:
- `'use client'`, `'use server'` (React)
- `'use cache'` (Next.js)
## Functions
See [functions.md](./functions.md) for:
- Navigation hooks: `useRouter`, `usePathname`, `useSearchParams`, `useParams`
- Server functions: `cookies`, `headers`, `draftMode`, `after`
- Generate functions: `generateStaticParams`, `generateMetadata`
## Error Handling
See [error-handling.md](./error-handling.md) for:
- `error.tsx`, `global-error.tsx`, `not-found.tsx`
- `redirect`, `permanentRedirect`, `notFound`
- `forbidden`, `unauthorized` (auth errors)
- `unstable_rethrow` for catch blocks
## Data Patterns
Project-specific: See [uuid-handling.md](./uuid-handling.md) for ADR-019 UUID handling patterns.
See [data-patterns.md](./data-patterns.md) for:
- Server Components vs Server Actions vs Route Handlers
- Avoiding data waterfalls (`Promise.all`, Suspense, preload)
- Client component data fetching
## Route Handlers
See [route-handlers.md](./route-handlers.md) for:
- `route.ts` basics
- GET handler conflicts with `page.tsx`
- Environment behavior (no React DOM)
- When to use vs Server Actions
## Metadata & OG Images
See [metadata.md](./metadata.md) for:
- Static and dynamic metadata
- `generateMetadata` function
- OG image generation with `next/og`
- File-based metadata conventions
## Image Optimization
See [image.md](./image.md) for:
- Always use `next/image` over `
`
- Remote images configuration
- Responsive `sizes` attribute
- Blur placeholders
- Priority loading for LCP
## Font Optimization
See [font.md](./font.md) for:
- `next/font` setup
- Google Fonts, local fonts
- Tailwind CSS integration
- Preloading subsets
## Bundling
See [bundling.md](./bundling.md) for:
- Server-incompatible packages
- CSS imports (not link tags)
- Polyfills (already included)
- ESM/CommonJS issues
- Bundle analysis
## Scripts
See [scripts.md](./scripts.md) for:
- `next/script` vs native script tags
- Inline scripts need `id`
- Loading strategies
- Google Analytics with `@next/third-parties`
## Hydration Errors
See [hydration-error.md](./hydration-error.md) for:
- Common causes (browser APIs, dates, invalid HTML)
- Debugging with error overlay
- Fixes for each cause
## Suspense Boundaries
See [suspense-boundaries.md](./suspense-boundaries.md) for:
- CSR bailout with `useSearchParams` and `usePathname`
- Which hooks require Suspense boundaries
## Parallel & Intercepting Routes
See [parallel-routes.md](./parallel-routes.md) for:
- Modal patterns with `@slot` and `(.)` interceptors
- `default.tsx` for fallbacks
- Closing modals correctly with `router.back()`
## Self-Hosting
See [self-hosting.md](./self-hosting.md) for:
- `output: 'standalone'` for Docker
- Cache handlers for multi-instance ISR
- What works vs needs extra setup
## NAP-DMS Project-Specific Rules (MUST FOLLOW)
These rules are mandatory for the NAP-DMS LCBP3 frontend project:
### State Management (บังคับใช้)
**Server State - TanStack Query (React Query)**
```tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// ❌ ห้ามใช้ useEffect โดยตรง
// ✅ ใช้ TanStack Query
export function useCorrespondences(projectId: string) {
return useQuery({
queryKey: ['correspondences', projectId],
queryFn: () => correspondenceService.getAll(projectId),
staleTime: 5 * 60 * 1000,
});
}
```
**Form State - React Hook Form + Zod**
```tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
const schema = z.object({
title: z.string().min(1, 'กรุณาระบุหัวเรื่อง'),
projectUuid: z.string().uuid('กรุณาเลือกโปรเจกต์'),
});
const form = useForm({
resolver: zodResolver(schema),
});
```
### ADR-019 UUID Handling (CRITICAL)
```tsx
// Interface ต้องมีทั้ง id และ publicId
interface Contract {
id?: number; // Internal (อาจ undefined)
publicId?: string; // UUID - ใช้ตัวนี้
contractCode: string;
}
// Select options - ใช้ pattern นี้เสมอ
const options = contracts.map((c) => ({
label: `${c.contractName} (${c.contractCode})`,
value: String(c.publicId ?? c.id ?? ''), // fallback pattern
key: String(c.publicId ?? c.id ?? ''),
}));
// ❌ ห้ามใช้ parseInt บน UUID
// const id = parseInt(projectId); // WRONG!
// ✅ ส่ง UUID string ตรงๆ
apiClient.get(`/projects/${projectId}`); // projectId is UUID string
```
### Naming Conventions
**Code Identifiers - ภาษาอังกฤษ**
```tsx
// ✅ Correct
interface Correspondence {
documentNumber: string;
createdAt: string;
}
// ❌ Wrong
interface เอกสาร {
เลขที่: string;
}
```
**Comments - ภาษาไทย**
```tsx
// ✅ Correct - อธิบาย logic เป็นภาษาไทย
// ตรวจสอบว่ามีการระบุ projectUuid หรือไม่
if (!data.projectUuid) {
throw new Error('กรุณาเลือกโปรเจกต์');
}
// ❌ Wrong - ห้ามใช้ภาษาอังกฤษใน comments
// Check if projectUuid is provided
```
### UI Components
**บังคับใช้ shadcn/ui**
```tsx
// ✅ Correct
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
// ❌ Wrong - ไม่สร้าง component เองถ้ามีใน shadcn
const MyButton = () => ;
```
### File Upload Pattern
```tsx
import { useDropzone } from 'react-dropzone';
// Two-phase upload
const onDrop = useCallback(async (files: File[]) => {
// Phase 1: Upload to temp
const tempFiles = await Promise.all(files.map((file) => uploadService.uploadTemp(file)));
setTempIds(tempFiles.map((f) => f.tempId));
}, []);
// Phase 2: Commit on form submit
const onSubmit = async (data: FormData) => {
await correspondenceService.create({
...data,
tempFileIds,
});
};
```
### API Client Setup
```typescript
// lib/api/client.ts
const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
timeout: 30000,
});
// Auto-add Idempotency-Key
apiClient.interceptors.request.use((config) => {
if (['post', 'put', 'patch'].includes(config.method?.toLowerCase() || '')) {
config.headers['Idempotency-Key'] = uuidv4();
}
return config;
});
```
### Anti-Patterns (ห้ามทำ)
- ❌ Fetch data ใน useEffect โดยตรง
- ❌ Props drilling ลึกเกิน 3 levels
- ❌ Inline styles (ใช้ Tailwind)
- ❌ console.log ใน production
- ❌ parseInt() บน UUID values
- ❌ ใช้ index เป็น key ใน list
- ❌ Snake_case ใน form field names (ใช้ camelCase)
---
See [debug-tricks.md](./debug-tricks.md) for:
- MCP endpoint for AI-assisted debugging
- Rebuild specific routes with `--debug-build-paths`