nextjs

Build Next.js 16 applications with App Router, Server/Client Components, data fetching, caching, and routing. Use when creating Next.js projects, pages, layouts, route handlers, server actions, or when asking about App Router patterns, RSC, streaming, or caching strategies.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "nextjs" with this command: npx skills add migueldialpad/skills/migueldialpad-skills-nextjs

Next.js (App Router)

Quick Start

# Create new project
npx create-next-app@latest my-app --yes
cd my-app && npm run dev

Default setup: TypeScript, Tailwind, ESLint, App Router, Turbopack.

Core Concepts

File-System Routing

Routes are defined by folder structure in app/:

FilePurpose
page.tsxPage UI (makes route public)
layout.tsxShared UI wrapper
loading.tsxLoading skeleton (Suspense)
error.tsxError boundary
not-found.tsx404 UI
route.tsAPI endpoint

Server vs Client Components

Server Components (default):

  • Fetch data, access DB/secrets
  • Reduce JS bundle
  • No state/effects/browser APIs

Client Components ('use client'):

  • State, effects, event handlers
  • Browser APIs
  • Interactivity
// Server Component (default)
export default async function Page() {
    const data = await fetch('https://api.example.com/data')
    return <ClientButton data={data} />
}

// Client Component
'use client'
import { useState } from 'react'
export function ClientButton({ data }) {
    const [count, setCount] = useState(0)
    return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Dynamic Routes

PatternExample URLUsage
[slug]/blog/helloSingle param
[...slug]/docs/a/b/cCatch-all
[[...slug]]/docs or /docs/aOptional catch-all
// app/blog/[slug]/page.tsx
export default async function Page({
    params
}: { params: Promise<{ slug: string }> }) {
    const { slug } = await params
    return <h1>{slug}</h1>
}

Route Groups & Private Folders

  • (group) - Organize without affecting URL
  • _folder - Private, not routable
app/
├── (marketing)/
│   └── page.tsx         → /
├── (dashboard)/
│   └── settings/
│       └── page.tsx     → /settings
└── _components/         → Not routable

Data Fetching

Server Components

// Direct fetch
export default async function Page() {
    const data = await fetch('https://api.example.com/data')
    return <div>{data.title}</div>
}

// With database
import { db } from '@/lib/db'
export default async function Page() {
    const posts = await db.select().from(posts)
    return <PostList posts={posts} />
}

Client Components

'use client'
import { use } from 'react'

// Via promise from server
export function Posts({ posts }: { posts: Promise<Post[]> }) {
    const data = use(posts)
    return <ul>{data.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}

Parallel Fetching

export default async function Page() {
    // Start both requests simultaneously
    const artistData = getArtist(id)
    const albumsData = getAlbums(id)
    
    const [artist, albums] = await Promise.all([artistData, albumsData])
    return <div>{artist.name}</div>
}

Caching & Revalidation

fetch Options

// Cache indefinitely
fetch(url, { cache: 'force-cache' })

// Revalidate every hour
fetch(url, { next: { revalidate: 3600 } })

// Tag for on-demand revalidation
fetch(url, { next: { tags: ['posts'] } })

use cache Directive

import { cacheTag, cacheLife } from 'next/cache'

export async function getProducts() {
    'use cache'
    cacheTag('products')
    cacheLife('hours')
    return db.query('SELECT * FROM products')
}

Revalidation

'use server'
import { revalidateTag, revalidatePath, updateTag } from 'next/cache'

export async function updateProduct() {
    await db.products.update(...)
    
    // Options:
    revalidateTag('products', 'max')  // Stale-while-revalidate
    updateTag('products')              // Immediate (Server Actions only)
    revalidatePath('/products')        // By path
}

Server Actions

// app/actions.ts
'use server'

export async function createPost(formData: FormData) {
    const title = formData.get('title')
    await db.post.create({ data: { title } })
    revalidateTag('posts')
}

// Usage in component
import { createPost } from './actions'

export function Form() {
    return (
        <form action={createPost}>
            <input name="title" />
            <button type="submit">Create</button>
        </form>
    )
}

Route Handlers (API)

// app/api/posts/route.ts
import { NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
    const searchParams = request.nextUrl.searchParams
    const query = searchParams.get('q')
    return Response.json({ data: [] })
}

export async function POST(request: Request) {
    const body = await request.json()
    return Response.json({ created: true }, { status: 201 })
}

// Dynamic route: app/api/posts/[id]/route.ts
export async function GET(
    request: Request,
    { params }: { params: Promise<{ id: string }> }
) {
    const { id } = await params
    return Response.json({ id })
}

Streaming & Suspense

import { Suspense } from 'react'

export default function Page() {
    return (
        <div>
            <h1>Dashboard</h1>
            <Suspense fallback={<Skeleton />}>
                <SlowComponent />
            </Suspense>
        </div>
    )
}

Or use loading.tsx for entire page streaming.

Components

Link

import Link from 'next/link'

<Link href="/dashboard">Dashboard</Link>
<Link href="/dashboard" replace>Replace history</Link>
<Link href="/dashboard" scroll={false}>Keep scroll</Link>
<Link href="/dashboard" prefetch={false}>No prefetch</Link>

Image

import Image from 'next/image'

// Local image (auto width/height)
import profilePic from './profile.png'
<Image src={profilePic} alt="Profile" />

// Remote image (explicit dimensions)
<Image
    src="https://example.com/photo.jpg"
    alt="Photo"
    width={500}
    height={300}
/>

// Fill container
<div style={{ position: 'relative', width: '100%', height: 300 }}>
    <Image src="/bg.jpg" alt="Background" fill style={{ objectFit: 'cover' }} />
</div>

Configure remote patterns in next.config.js:

module.exports = {
    images: {
        remotePatterns: [
            new URL('https://example.com/**')
        ]
    }
}

Metadata

// Static
export const metadata = {
    title: 'My Page',
    description: 'Page description'
}

// Dynamic
export async function generateMetadata({ params }) {
    const { id } = await params
    const product = await getProduct(id)
    return { title: product.name }
}

Additional Resources

Reference Links

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

mermaid-charts

No summary provided by upstream source.

Repository SourceNeeds Review
General

bunjs

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

Self Updater

⭐ OPEN SOURCE! GitHub: github.com/GhostDragon124/openclaw-self-updater ⭐ ONLY skill with Cron-aware + Idle detection! Auto-updates OpenClaw core & skills, an...

Registry SourceRecently Updated
1171Profile unavailable
Coding

xCloud Docker Deploy

Deploy any project to xCloud hosting — auto-detects stack (WordPress, Laravel, PHP, Node.js, Next.js, NestJS, Python, Go, Rust), routes to native or Docker d...

Registry SourceRecently Updated
1920Profile unavailable