547 lines
12 KiB
Markdown
547 lines
12 KiB
Markdown
---
|
|
name: nextjs
|
|
description: 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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
'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**:
|
|
```typescript
|
|
// 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)**:
|
|
```typescript
|
|
// 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)**:
|
|
```typescript
|
|
// 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)**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
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):
|
|
```typescript
|
|
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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
'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**:
|
|
```typescript
|
|
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**:
|
|
```typescript
|
|
'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**:
|
|
```typescript
|
|
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**:
|
|
```typescript
|
|
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**:
|
|
```typescript
|
|
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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
// 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**:
|
|
```typescript
|
|
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**:
|
|
```javascript
|
|
// next.config.js
|
|
module.exports = {
|
|
images: {
|
|
remotePatterns: [
|
|
{
|
|
protocol: 'https',
|
|
hostname: 'images.example.com',
|
|
},
|
|
],
|
|
formats: ['image/avif', 'image/webp'],
|
|
},
|
|
};
|
|
```
|
|
|
|
### 10. Performance Optimization
|
|
|
|
**Code Splitting**:
|
|
```typescript
|
|
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**:
|
|
```typescript
|
|
import { Suspense } from 'react';
|
|
|
|
export default function Page() {
|
|
return (
|
|
<div>
|
|
<h1>Dashboard</h1>
|
|
<Suspense fallback={<LoadingSkeleton />}>
|
|
<SlowDataComponent />
|
|
</Suspense>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Font Optimization**:
|
|
```typescript
|
|
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**:
|
|
```javascript
|
|
/** @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
|
|
|
|
1. **Server Components by Default**: Use Client Components only when needed
|
|
2. **Streaming**: Use Suspense for better perceived performance
|
|
3. **Image Optimization**: Always use next/image
|
|
4. **Font Optimization**: Use next/font for automatic optimization
|
|
5. **Metadata**: Use generateMetadata for dynamic SEO
|
|
6. **Caching**: Leverage ISR and revalidation strategies
|
|
7. **Type Safety**: Enable TypeScript strict mode and typed routes
|
|
8. **Security Headers**: Configure in next.config.js
|
|
9. **Error Handling**: Implement error.tsx for error boundaries
|
|
10. **Loading States**: Add loading.tsx for better UX
|
|
|
|
You are ready to build high-performance Next.js applications!
|