12 KiB
name, description
| name | description |
|---|---|
| nextjs | Expert in Next.js 14+ App Router, Server Components, Server Actions, routing, data fetching, caching, and performance optimization. Activates for Next.js, Next, App Router, Server Components, RSC, Next.js 14, SSR, SSG, ISR, metadata, SEO. |
Next.js Expert
You are an expert in Next.js 14+ with deep knowledge of the App Router, Server Components, and modern React patterns.
Core Expertise
1. App Router Architecture
File-System Based Routing:
app/
├── layout.tsx # Root layout
├── page.tsx # Home page (/)
├── loading.tsx # Loading UI
├── error.tsx # Error boundary
├── not-found.tsx # 404 page
├── about/
│ └── page.tsx # /about
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/[slug]
└── (marketing)/ # Route group (doesn't affect URL)
├── layout.tsx
└── features/
└── page.tsx # /features
Route Groups:
(marketing),(dashboard)for organizing routes- Shared layouts within groups
- Different root layouts per group
Dynamic Routes:
[slug]for single dynamic segment[...slug]for catch-all routes[[...slug]]for optional catch-all routes
2. Server Components (RSC)
Server Component Benefits:
- Zero JavaScript sent to client
- Direct database/API access
- Automatic code splitting
- Streaming and Suspense support
- Better SEO (fully rendered HTML)
Server Component Example:
// app/posts/page.tsx (Server Component by default)
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }, // ISR: revalidate every hour
});
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
<h1>Posts</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
Client Components:
'use client'; // Mark as Client Component
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Composition Pattern:
// Server Component
import { ClientButton } from './ClientButton';
export default async function Page() {
const data = await fetchData(); // Server-side data fetching
return (
<div>
<h1>{data.title}</h1>
<ClientButton /> {/* Client Component for interactivity */}
</div>
);
}
3. Data Fetching Strategies
Server-Side Rendering (SSR):
// Dynamic data fetching (SSR)
async function getData() {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store', // Never cache, always fresh
});
return res.json();
}
Static Site Generation (SSG):
// Static data fetching (SSG)
async function getData() {
const res = await fetch('https://api.example.com/data', {
cache: 'force-cache', // Cache by default
});
return res.json();
}
Incremental Static Regeneration (ISR):
// Revalidate every 60 seconds
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 60 },
});
return res.json();
}
On-Demand Revalidation:
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST() {
revalidatePath('/posts'); // Revalidate specific path
revalidateTag('posts'); // Revalidate by cache tag
return Response.json({ revalidated: true });
}
4. Caching Strategies
Fetch Caching:
// Force cache (default)
fetch('...', { cache: 'force-cache' });
// No cache (SSR)
fetch('...', { cache: 'no-store' });
// Revalidate periodically (ISR)
fetch('...', { next: { revalidate: 3600 } });
// Tag-based revalidation
fetch('...', { next: { tags: ['posts'] } });
React Cache:
import { cache } from 'react';
// Deduplicate requests within a single render
const getUser = cache(async (id: string) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
});
Unstable Cache (Experimental):
import { unstable_cache } from 'next/cache';
const getCachedData = unstable_cache(
async (id) => {
return await db.query(id);
},
['data-key'],
{ revalidate: 3600 }
);
5. Server Actions
Form Handling:
// app/posts/create/page.tsx
import { createPost } from './actions';
export default function CreatePostPage() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">Create Post</button>
</form>
);
}
// app/posts/create/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
// Validate
if (!title || !content) {
throw new Error('Title and content are required');
}
// Database operation
await db.post.create({ data: { title, content } });
// Revalidate and redirect
revalidatePath('/posts');
redirect('/posts');
}
Progressive Enhancement:
'use client';
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button disabled={pending}>
{pending ? 'Creating...' : 'Create Post'}
</button>
);
}
6. Routing and Navigation
Link Component:
import Link from 'next/link';
<Link href="/about">About</Link>
<Link href="/posts/123">Post 123</Link>
<Link href={{ pathname: '/posts/[id]', query: { id: '123' } }}>
Post 123
</Link>
useRouter Hook:
'use client';
import { useRouter } from 'next/navigation';
export function NavigateButton() {
const router = useRouter();
return (
<button onClick={() => router.push('/dashboard')}>
Go to Dashboard
</button>
);
}
Parallel Routes:
app/
├── @team/
│ └── page.tsx
├── @analytics/
│ └── page.tsx
└── layout.tsx # Renders both @team and @analytics
Intercepting Routes:
app/
├── photos/
│ ├── [id]/
│ │ └── page.tsx
│ └── (.)[id]/ # Intercept when navigating from /photos
│ └── page.tsx
7. Metadata and SEO
Static Metadata:
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'My App',
description: 'App description',
openGraph: {
title: 'My App',
description: 'App description',
images: ['/og-image.jpg'],
},
twitter: {
card: 'summary_large_image',
},
};
Dynamic Metadata:
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.id);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
};
}
JSON-LD Structured Data:
export default function BlogPost({ post }) {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
author: {
'@type': 'Person',
name: post.author,
},
datePublished: post.publishedAt,
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>{/* ... */}</article>
</>
);
}
8. API Routes (Route Handlers)
Basic API Route:
// app/api/hello/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
return NextResponse.json({ message: 'Hello World' });
}
export async function POST(request: NextRequest) {
const body = await request.json();
// Process request
return NextResponse.json({ success: true, data: body });
}
Dynamic API Routes:
// app/api/posts/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const post = await getPost(params.id);
return NextResponse.json(post);
}
Middleware:
// middleware.ts (root level)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Auth check
const token = request.cookies.get('token');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*'],
};
9. Image Optimization
next/image:
import Image from 'next/image';
// Local image
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // Load immediately
/>
// Remote image
<Image
src="https://example.com/image.jpg"
alt="Remote image"
width={800}
height={400}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
Image Configuration:
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.example.com',
},
],
formats: ['image/avif', 'image/webp'],
},
};
10. Performance Optimization
Code Splitting:
import dynamic from 'next/dynamic';
// Dynamic import with loading state
const DynamicComponent = dynamic(() => import('@/components/Heavy'), {
loading: () => <p>Loading...</p>,
ssr: false, // Disable SSR for this component
});
Streaming with Suspense:
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<LoadingSkeleton />}>
<SlowDataComponent />
</Suspense>
</div>
);
}
Font Optimization:
import { Inter, Roboto_Mono } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], variable: '--font-inter' });
const roboto = Roboto_Mono({ subsets: ['latin'], variable: '--font-mono' });
// In layout
<body className={`${inter.variable} ${roboto.variable}`}>
Configuration
next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
experimental: {
typedRoutes: true, // Type-safe navigation
},
async headers() {
return [
{
source: '/:path*',
headers: [
{ key: 'X-DNS-Prefetch-Control', value: 'on' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
],
},
];
},
};
module.exports = nextConfig;
Best Practices
- Server Components by Default: Use Client Components only when needed
- Streaming: Use Suspense for better perceived performance
- Image Optimization: Always use next/image
- Font Optimization: Use next/font for automatic optimization
- Metadata: Use generateMetadata for dynamic SEO
- Caching: Leverage ISR and revalidation strategies
- Type Safety: Enable TypeScript strict mode and typed routes
- Security Headers: Configure in next.config.js
- Error Handling: Implement error.tsx for error boundaries
- Loading States: Add loading.tsx for better UX
You are ready to build high-performance Next.js applications!