home / skills / anton-abyzov / specweave / nextjs

nextjs skill

/plugins/specweave-frontend/skills/nextjs

This skill helps you build high-performance Next.js 14+ apps by leveraging App Router, Server Components, and advanced data fetching and caching strategies.

This is most likely a fork of the sw-nextjs skill from openclaw
npx playbooks add skill anton-abyzov/specweave --skill nextjs

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
11.7 KB
---
name: nextjs
description: Expert in Next.js 14+ App Router, Server Components, and Server Actions. Use when building Next.js applications, implementing SSR/SSG, or configuring dynamic routing and data fetching. Covers streaming, caching strategies, middleware, and deployment optimization.
---

# 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!

Overview

This skill is an expert guide for building production-grade Next.js 14+ applications using the App Router, Server Components, and Server Actions. It focuses on modern patterns for routing, data fetching (SSR/SSG/ISR), streaming, caching, middleware, and deploy-time optimizations. Use it to design fast, SEO-friendly, and maintainable Next.js projects with TypeScript best practices.

How this skill works

The skill inspects app/ directory patterns, route groups, dynamic and parallel routes, and the distinction between Server and Client Components. It explains data-fetching modes (cache options, revalidate, no-store), tag-based and path revalidation, and server actions for form handling and progressive enhancement. It also covers middleware, API route handlers, image and font optimization, metadata generation, and deployment-related config in next.config.js.

When to use it

  • When designing a new Next.js 14+ app with the App Router and server-first principles
  • When implementing SSR, SSG, or ISR data strategies and revalidation
  • When adding Server Actions for secure form handling and direct DB access
  • When optimizing performance: streaming, Suspense, code-splitting, and image/fonts
  • When configuring middleware, API route handlers, and secure headers
  • When preparing TypeScript, SEO metadata, and deployment configuration

Best practices

  • Prefer Server Components by default; mark only interactive parts with 'use client'
  • Use fetch cache options and next revalidate tags for predictable ISR and cache control
  • Leverage Suspense and streaming for progressive page rendering and better UX
  • Keep image and font optimization via next/image and next/font for performance and CLS
  • Implement error.tsx and loading.tsx per route to handle failures and loading states
  • Enable TypeScript strict mode and typed routes; add security headers in next.config.js

Example use cases

  • A marketing site with static pages (SSG) and dynamic blog posts using generateMetadata for SEO
  • A dashboard with protected routes via middleware, server-side data fetching, and streaming widgets
  • An e-commerce product page that uses ISR for price updates and tag-based revalidation on stock changes
  • A form-driven CMS using Server Actions to create content, revalidate paths, and redirect after submit
  • An analytics layout that renders parallel routes (e.g., @team and @analytics) inside a shared layout

FAQ

When should I use Server Actions instead of client API calls?

Use Server Actions for form submission and sensitive operations that benefit from direct server access to databases or secrets and for simpler progressive enhancement without custom API endpoints.

How do I choose between cache: 'no-store', force-cache, and next.revalidate?

Use 'no-store' for always-fresh SSR data, 'force-cache' for SSG static content, and next revalidate (ISR) when you need periodic background updates with predictable staleness.