Initial commit
This commit is contained in:
546
skills/nextjs/SKILL.md
Normal file
546
skills/nextjs/SKILL.md
Normal file
@@ -0,0 +1,546 @@
|
||||
---
|
||||
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!
|
||||
Reference in New Issue
Block a user