nextjs

SKILL.md

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

  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!

Weekly Installs
9
Installed on
claude-code8
opencode6
cursor6
codex6
antigravity6
gemini-cli6