| 1 |
Routing |
Use App Router for new projects |
App Router is the recommended approach in Next.js 14+ |
app/ directory with page.tsx |
pages/ for new projects |
app/dashboard/page.tsx |
pages/dashboard.tsx |
Medium |
https://nextjs.org/docs/app |
| 2 |
Routing |
Use file-based routing |
Create routes by adding files in app directory |
page.tsx for routes layout.tsx for layouts |
Manual route configuration |
app/blog/[slug]/page.tsx |
Custom router setup |
Medium |
https://nextjs.org/docs/app/building-your-application/routing |
| 3 |
Routing |
Colocate related files |
Keep components styles tests with their routes |
Component files alongside page.tsx |
Separate components folder |
app/dashboard/_components/ |
components/dashboard/ |
Low |
|
| 4 |
Routing |
Use route groups for organization |
Group routes without affecting URL |
Parentheses for route groups |
Nested folders affecting URL |
(marketing)/about/page.tsx |
marketing/about/page.tsx |
Low |
https://nextjs.org/docs/app/building-your-application/routing/route-groups |
| 5 |
Routing |
Handle loading states |
Use loading.tsx for route loading UI |
loading.tsx alongside page.tsx |
Manual loading state management |
app/dashboard/loading.tsx |
useState for loading in page |
Medium |
https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming |
| 6 |
Routing |
Handle errors with error.tsx |
Catch errors at route level |
error.tsx with reset function |
try/catch in every component |
app/dashboard/error.tsx |
try/catch in page component |
High |
https://nextjs.org/docs/app/building-your-application/routing/error-handling |
| 7 |
Rendering |
Use Server Components by default |
Server Components reduce client JS bundle |
Keep components server by default |
Add 'use client' unnecessarily |
export default function Page() |
('use client') for static content |
High |
https://nextjs.org/docs/app/building-your-application/rendering/server-components |
| 8 |
Rendering |
Mark Client Components explicitly |
'use client' for interactive components |
Add 'use client' only when needed |
Server Component with hooks/events |
('use client') for onClick useState |
No directive with useState |
High |
https://nextjs.org/docs/app/building-your-application/rendering/client-components |
| 9 |
Rendering |
Push Client Components down |
Keep Client Components as leaf nodes |
Client wrapper for interactive parts only |
Mark page as Client Component |
in Server Page |
('use client') on page.tsx |
High |
|
| 10 |
Rendering |
Use streaming for better UX |
Stream content with Suspense boundaries |
Suspense for slow data fetches |
Wait for all data before render |
|
await allData then render |
Medium |
https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming |
| 11 |
Rendering |
Choose correct rendering strategy |
SSG for static SSR for dynamic ISR for semi-static |
generateStaticParams for known paths |
SSR for static content |
export const revalidate = 3600 |
fetch without cache config |
Medium |
|
| 12 |
DataFetching |
Fetch data in Server Components |
Fetch directly in async Server Components |
async function Page() { const data = await fetch() } |
useEffect for initial data |
const data = await fetch(url) |
useEffect(() => fetch(url)) |
High |
https://nextjs.org/docs/app/building-your-application/data-fetching |
| 13 |
DataFetching |
Configure caching explicitly (Next.js 15+) |
Next.js 15 changed defaults to uncached for fetch |
Explicitly set cache: 'force-cache' for static data |
Assume default is cached (it's not in Next.js 15) |
fetch(url, { cache: 'force-cache' }) |
fetch(url) // Uncached in v15 |
High |
https://nextjs.org/docs/app/building-your-application/upgrading/version-15 |
| 14 |
DataFetching |
Deduplicate fetch requests |
React and Next.js dedupe same requests |
Same fetch call in multiple components |
Manual request deduplication |
Multiple components fetch same URL |
Custom cache layer |
Low |
|
| 15 |
DataFetching |
Use Server Actions for mutations |
Server Actions for form submissions |
action={serverAction} in forms |
API route for every mutation |
|
|
Medium |
https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations |
| 16 |
DataFetching |
Revalidate data appropriately |
Use revalidatePath/revalidateTag after mutations |
Revalidate after Server Action |
'use client' with manual refetch |
revalidatePath('/posts') |
router.refresh() everywhere |
Medium |
https://nextjs.org/docs/app/building-your-application/caching#revalidating |
| 17 |
Images |
Use next/image for optimization |
Automatic image optimization and lazy loading |
component for all images |
tags directly |
 |
 |
High |
https://nextjs.org/docs/app/building-your-application/optimizing/images |
| 18 |
Images |
Provide width and height |
Prevent layout shift with dimensions |
width and height props or fill |
Missing dimensions |
![]() |
 |
High |
|
| 19 |
Images |
Use fill for responsive images |
Fill container with object-fit |
fill prop with relative parent |
Fixed dimensions for responsive |
![]() |
![]() |
Medium |
|
| 20 |
Images |
Configure remote image domains |
Whitelist external image sources |
remotePatterns in next.config.js |
Allow all domains |
remotePatterns: [{ hostname: 'cdn.example.com' }] |
domains: ['*'] |
High |
https://nextjs.org/docs/app/api-reference/components/image#remotepatterns |
| 21 |
Images |
Use priority for LCP images |
Mark above-fold images as priority |
priority prop on hero images |
All images with priority |
 |
on every image |
Medium |
|
| 22 |
Fonts |
Use next/font for fonts |
Self-hosted fonts with zero layout shift |
next/font/google or next/font/local |
External font links |
import { Inter } from 'next/font/google' |
|
Medium |
https://nextjs.org/docs/app/building-your-application/optimizing/fonts |
| 23 |
Fonts |
Apply font to layout |
Set font in root layout for consistency |
className on body in layout.tsx |
Font in individual pages |
|
Each page imports font |
Low |
|
| 24 |
Fonts |
Use variable fonts |
Variable fonts reduce bundle size |
Single variable font file |
Multiple font weights as files |
Inter({ subsets: ['latin'] }) |
Inter_400 Inter_500 Inter_700 |
Low |
|
| 25 |
Metadata |
Use generateMetadata for dynamic |
Generate metadata based on params |
export async function generateMetadata() |
Hardcoded metadata everywhere |
generateMetadata({ params }) |
export const metadata = {} |
Medium |
https://nextjs.org/docs/app/building-your-application/optimizing/metadata |
| 26 |
Metadata |
Include OpenGraph images |
Add OG images for social sharing |
opengraph-image.tsx or og property |
Missing social preview images |
opengraph: { images: ['/og.png'] } |
No OG configuration |
Medium |
|
| 27 |
Metadata |
Use metadata API |
Export metadata object for static metadata |
export const metadata = {} |
Manual head tags |
export const metadata = { title: 'Page' } |
<head></head> |
Medium |
|
| 28 |
API |
Use Route Handlers for APIs |
app/api routes for API endpoints |
app/api/users/route.ts |
pages/api for new projects |
export async function GET(request) |
export default function handler |
Medium |
https://nextjs.org/docs/app/building-your-application/routing/route-handlers |
| 29 |
API |
Return proper Response objects |
Use NextResponse for API responses |
NextResponse.json() for JSON |
Plain objects or res.json() |
return NextResponse.json({ data }) |
return { data } |
Medium |
|
| 30 |
API |
Handle HTTP methods explicitly |
Export named functions for methods |
Export GET POST PUT DELETE |
Single handler for all methods |
export async function POST() |
switch(req.method) |
Low |
|
| 31 |
API |
Validate request body |
Validate input before processing |
Zod or similar for validation |
Trust client input |
const body = schema.parse(await req.json()) |
const body = await req.json() |
High |
|
| 32 |
Middleware |
Use middleware for auth |
Protect routes with middleware.ts |
middleware.ts at root |
Auth check in every page |
export function middleware(request) |
if (!session) redirect in page |
Medium |
https://nextjs.org/docs/app/building-your-application/routing/middleware |
| 33 |
Middleware |
Match specific paths |
Configure middleware matcher |
config.matcher for specific routes |
Run middleware on all routes |
matcher: ['/dashboard/:path*'] |
No matcher config |
Medium |
|
| 34 |
Middleware |
Keep middleware edge-compatible |
Middleware runs on Edge runtime |
Edge-compatible code only |
Node.js APIs in middleware |
Edge-compatible auth check |
fs.readFile in middleware |
High |
|
| 35 |
Environment |
Use NEXT_PUBLIC prefix |
Client-accessible env vars need prefix |
NEXT_PUBLIC_ for client vars |
Server vars exposed to client |
NEXT_PUBLIC_API_URL |
API_SECRET in client code |
High |
https://nextjs.org/docs/app/building-your-application/configuring/environment-variables |
| 36 |
Environment |
Validate env vars |
Check required env vars exist |
Validate on startup |
Undefined env at runtime |
if (!process.env.DATABASE_URL) throw |
process.env.DATABASE_URL (might be undefined) |
High |
|
| 37 |
Environment |
Use .env.local for secrets |
Local env file for development secrets |
.env.local gitignored |
Secrets in .env committed |
.env.local with secrets |
.env with DATABASE_PASSWORD |
High |
|
| 38 |
Performance |
Analyze bundle size |
Use @next/bundle-analyzer |
Bundle analyzer in dev |
Ship large bundles blindly |
ANALYZE=true npm run build |
No bundle analysis |
Medium |
https://nextjs.org/docs/app/building-your-application/optimizing/bundle-analyzer |
| 39 |
Performance |
Use dynamic imports |
Code split with next/dynamic |
dynamic() for heavy components |
Import everything statically |
const Chart = dynamic(() => import('./Chart')) |
import Chart from './Chart' |
Medium |
https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading |
| 40 |
Performance |
Avoid layout shifts |
Reserve space for dynamic content |
Skeleton loaders aspect ratios |
Content popping in |
|
No placeholder for async content |
High |
|
| 41 |
Performance |
Use Partial Prerendering |
Combine static and dynamic in one route |
Static shell with Suspense holes |
Full dynamic or static pages |
Static header + dynamic content |
Entire page SSR |
Low |
https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering |
| 42 |
Link |
Use next/link for navigation |
Client-side navigation with prefetching |
for internal links |
for internal navigation |
About |
About |
High |
https://nextjs.org/docs/app/api-reference/components/link |
| 43 |
Link |
Prefetch strategically |
Control prefetching behavior |
prefetch={false} for low-priority |
Prefetch all links |
|
Default prefetch on every link |
Low |
|
| 44 |
Link |
Use scroll option appropriately |
Control scroll behavior on navigation |
scroll={false} for tabs pagination |
Always scroll to top |
|
Manual scroll management |
Low |
|
| 45 |
Config |
Use next.config.js correctly |
Configure Next.js behavior |
Proper config options |
Deprecated or wrong options |
images: { remotePatterns: [] } |
images: { domains: [] } |
Medium |
https://nextjs.org/docs/app/api-reference/next-config-js |
| 46 |
Config |
Enable strict mode |
Catch potential issues early |
reactStrictMode: true |
Strict mode disabled |
reactStrictMode: true |
reactStrictMode: false |
Medium |
|
| 47 |
Config |
Configure redirects and rewrites |
Use config for URL management |
redirects() rewrites() in config |
Manual redirect handling |
redirects: async () => [...] |
res.redirect in pages |
Medium |
https://nextjs.org/docs/app/api-reference/next-config-js/redirects |
| 48 |
Deployment |
Use Vercel for easiest deploy |
Vercel optimized for Next.js |
Deploy to Vercel |
Self-host without knowledge |
vercel deploy |
Complex Docker setup for simple app |
Low |
https://nextjs.org/docs/app/building-your-application/deploying |
| 49 |
Deployment |
Configure output for self-hosting |
Set output option for deployment target |
output: 'standalone' for Docker |
Default output for containers |
output: 'standalone' |
No output config for Docker |
Medium |
https://nextjs.org/docs/app/building-your-application/deploying#self-hosting |
| 50 |
Security |
Sanitize user input |
Never trust user input |
Escape sanitize validate all input |
Direct interpolation of user data |
DOMPurify.sanitize(userInput) |
dangerouslySetInnerHTML={{ __html: userInput }} |
High |
|
| 51 |
Security |
Use CSP headers |
Content Security Policy for XSS protection |
Configure CSP in next.config.js |
No security headers |
headers() with CSP |
No CSP configuration |
High |
https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy |
| 52 |
Security |
Validate Server Action input |
Server Actions are public endpoints |
Validate and authorize in Server Action |
Trust Server Action input |
Auth check + validation in action |
Direct database call without check |
High |
|