nextjs

Next.js Framework Guide

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 ar4mirez/samuel/ar4mirez-samuel-nextjs

Next.js Framework Guide

Framework: Next.js 14+ (App Router) Language: TypeScript/JavaScript Use Cases: Full-Stack Web Apps, SSR/SSG, E-commerce, Blogs, Dashboards

Overview

Next.js is a React framework providing server-side rendering, static site generation, API routes, and full-stack development in a single codebase. Version 14+ uses the App Router as the default, built on React Server Components.

Project Setup

Create new Next.js app

npx create-next-app@latest my-app --typescript --tailwind --eslint --app

cd my-app npm run dev

Recommended Project Structure

my-app/ ├── app/ │ ├── (auth)/ # Route group (no URL segment) │ │ ├── login/page.tsx │ │ └── register/page.tsx │ ├── dashboard/ │ │ ├── page.tsx # /dashboard │ │ ├── loading.tsx # Loading UI │ │ ├── error.tsx # Error boundary │ │ └── layout.tsx # Dashboard layout │ ├── api/ │ │ └── users/route.ts # API route handler │ ├── globals.css │ ├── layout.tsx # Root layout (required) │ └── page.tsx # Home page (/) ├── components/ │ ├── ui/ # Reusable UI components │ └── features/ # Feature-specific components ├── lib/ │ ├── db.ts # Database client │ └── utils.ts # Utility functions ├── hooks/ # Custom React hooks ├── types/ # TypeScript type definitions ├── public/ # Static assets ├── middleware.ts # Edge middleware ├── next.config.js ├── tailwind.config.ts └── package.json

Routing (App Router)

File-Based Routing Conventions

File Purpose

page.tsx

Route UI (makes segment publicly accessible)

layout.tsx

Shared layout (wraps children, persists)

loading.tsx

Loading UI (Suspense boundary)

error.tsx

Error boundary (must be 'use client' )

not-found.tsx

404 UI for this segment

route.ts

API route handler (GET, POST, etc.)

template.tsx

Like layout but re-mounts on navigation

default.tsx

Fallback for parallel routes

Route Patterns

app/ ├── page.tsx # / ├── about/page.tsx # /about ├── blog/ │ ├── page.tsx # /blog │ └── [slug]/page.tsx # /blog/:slug (dynamic) ├── shop/ │ └── [...categories]/page.tsx # /shop/a/b/c (catch-all) ├── (marketing)/ # Route group (no URL impact) │ ├── pricing/page.tsx # /pricing │ └── features/page.tsx # /features └── @modal/ # Parallel route (named slot) └── login/page.tsx

Page Component with Params

// app/blog/[slug]/page.tsx interface PageProps { params: { slug: string }; searchParams: { [key: string]: string | string[] | undefined }; }

export default function BlogPost({ params, searchParams }: PageProps) { return <article><h1>Post: {params.slug}</h1></article>; }

export async function generateMetadata({ params }: PageProps): Promise<Metadata> { const post = await getPost(params.slug); return { title: post.title, description: post.excerpt }; }

Layouts

// app/layout.tsx -- Root Layout (required, wraps entire app) import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = { title: { default: 'My App', template: '%s | My App' }, description: 'My application', };

export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body className={inter.className}>{children}</body> </html> ); }

Nested layouts compose automatically. Dashboard layout wraps all /dashboard/* routes:

// app/dashboard/layout.tsx export default function DashboardLayout({ children }: { children: React.ReactNode }) { return ( <div className="flex"> <Sidebar /> <div className="flex-1">{children}</div> </div> ); }

Server Components vs Client Components

Decision Rule

Need Component Type

Fetch data, access backend resources Server (default)

Static rendering, SEO content Server

Use hooks (useState, useEffect, etc.) Client

Browser APIs (window, localStorage) Client

Event handlers (onClick, onChange) Client

Third-party client-only libraries Client

Server Component (Default)

All components in the app/ directory are Server Components by default. They run on the server only and can directly access databases, file systems, and secrets.

// app/users/page.tsx -- Server Component (no directive needed) import { db } from '@/lib/db';

export default async function UsersPage() { const users = await db.user.findMany(); return ( <ul> {users.map((user) => <li key={user.id}>{user.name}</li>)} </ul> ); }

Client Component

Add 'use client' at the top of the file. Push this directive as low in the tree as possible.

// components/Counter.tsx 'use client';

import { useState } from 'react';

export function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>Count: {count}</button>; }

Composition Pattern

Fetch data in Server Components, pass to Client Components as props:

// app/dashboard/page.tsx (Server Component) import { ClientSidebar } from '@/components/ClientSidebar'; import { db } from '@/lib/db';

export default async function Dashboard() { const stats = await db.stats.get(); return ( <div> <ClientSidebar initialStats={stats} /> <DashboardContent stats={stats} /> </div> ); }

Data Fetching

Server Component Fetch with Caching

async function getProducts() { const res = await fetch('https://api.example.com/products', { next: { revalidate: 3600 }, // ISR: revalidate every hour }); if (!res.ok) throw new Error('Failed to fetch products'); return res.json(); }

Fetch Caching Options

Option Behavior

{ cache: 'force-cache' }

Static (default for GET)

{ cache: 'no-store' }

Dynamic (no caching)

{ next: { revalidate: N } }

ISR (revalidate every N seconds)

{ next: { tags: ['posts'] } }

Tag-based revalidation

Parallel Fetching

Always fetch independent data in parallel with Promise.all :

export default async function Dashboard({ params }: { params: { id: string } }) { const [user, orders] = await Promise.all([ getUser(params.id), getOrders(params.id), ]); return <div><UserProfile user={user} /><OrderList orders={orders} /></div>; }

Streaming with Suspense

import { Suspense } from 'react';

export default function Dashboard() { return ( <div> <WelcomeMessage /> <Suspense fallback={<StatsSkeleton />}> <Stats /> </Suspense> <Suspense fallback={<OrdersSkeleton />}> <RecentOrders /> </Suspense> </div> ); }

Server Actions

Define mutations with 'use server' . They run on the server and can be called from forms or client code.

// app/actions.ts 'use server';

import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import { z } from 'zod';

const createPostSchema = z.object({ title: z.string().min(1), content: z.string().min(10), });

export async function createPost(formData: FormData) { const validated = createPostSchema.parse({ title: formData.get('title'), content: formData.get('content'), }); await db.post.create({ data: validated }); revalidatePath('/posts'); redirect(/posts); }

Use in a form (no client JavaScript required for basic submissions):

export default function NewPost() { return ( <form action={createPost}> <input name="title" required /> <textarea name="content" required /> <button type="submit">Create</button> </form> ); }

API Route Handlers

// app/api/users/route.ts import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod';

const userSchema = z.object({ name: z.string().min(2), email: z.string().email(), });

export async function GET(request: NextRequest) { const page = parseInt(request.nextUrl.searchParams.get('page') || '1'); const users = await db.user.findMany({ skip: (page - 1) * 10, take: 10 }); return NextResponse.json(users); }

export async function POST(request: NextRequest) { try { const body = await request.json(); const validated = userSchema.parse(body); const user = await db.user.create({ data: validated }); return NextResponse.json(user, { status: 201 }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json({ errors: error.errors }, { status: 400 }); } return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } }

Middleware

Runs at the edge before every matched request. Use for auth checks, redirects, headers.

// middleware.ts (project root) import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) { const token = request.cookies.get('session')?.value; const isProtected = request.nextUrl.pathname.startsWith('/dashboard');

if (isProtected && !token) { return NextResponse.redirect(new URL('/login', request.url)); }

return NextResponse.next(); }

export const config = { matcher: ['/dashboard/:path*', '/api/:path*'], };

Error Handling

Error Boundary (error.tsx )

// app/dashboard/error.tsx 'use client';

export default function Error({ error, reset }: { error: Error; reset: () => void }) { return ( <div> <h2>Something went wrong</h2> <button onClick={reset}>Try again</button> </div> ); }

Not Found

// app/not-found.tsx import Link from 'next/link';

export default function NotFound() { return ( <div> <h2>Not Found</h2> <Link href="/">Return Home</Link> </div> ); }

Trigger programmatically: import { notFound } from 'next/navigation'; notFound();

Configuration

// next.config.js /** @type {import('next').NextConfig} / const nextConfig = { images: { remotePatterns: [{ protocol: 'https', hostname: '**.example.com' }], }, async redirects() { return [{ source: '/old', destination: '/new', permanent: true }]; }, async headers() { return [{ source: '/api/:path', headers: [{ key: 'Access-Control-Allow-Origin', value: '*' }], }]; }, }; module.exports = nextConfig;

Guardrails

  • Use Server Components by default; add 'use client' only when needed

  • Push 'use client' as low in the component tree as possible

  • Colocate data fetching with the component that needs it

  • Use Promise.all for independent parallel fetches

  • Implement loading.tsx and error.tsx for every route segment

  • Use Server Actions for mutations (not API routes for form submissions)

  • Validate all inputs with schema validators (Zod) in Server Actions and API routes

  • Use next/image for images and next/font for fonts (performance)

  • Set proper metadata on every page for SEO

  • Use Suspense boundaries to stream slow data

  • Never import server-only modules in Client Components

  • Never expose secrets or database access in Client Components

Commands Reference

npm run dev # Development server (http://localhost:3000) npm run build # Production build npm run start # Start production server npm run lint # ESLint check npx tsc --noEmit # TypeScript validation npm test # Run tests (Vitest/Jest)

Advanced Topics

For detailed code examples, advanced patterns, testing strategies, performance optimization, caching strategies, and ISR/SSG/SSR details, see:

  • references/patterns.md -- Authentication, advanced Server Actions, testing, performance, caching, rendering strategies, deployment

External References

  • Next.js Documentation

  • Next.js App Router

  • NextAuth.js / Auth.js

  • Vercel Deployment

  • React Server Components

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

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

blazor

No summary provided by upstream source.

Repository SourceNeeds Review
General

go-guide

No summary provided by upstream source.

Repository SourceNeeds Review