| name | nextjs |
| description | Next.js development with App Router, Server Components, API routes, and full-stack patterns |
| category | frameworks |
| triggers | ["nextjs","next.js","next","app router","server components","rsc"] |
Next.js
Modern Next.js development with App Router following industry best practices. This skill covers Server Components, data fetching, API routes, middleware, authentication, and full-stack patterns.
Purpose
Build production-ready Next.js applications:
- Master App Router architecture
- Implement Server and Client Components
- Handle data fetching and caching
- Create type-safe API routes
- Implement authentication and middleware
- Optimize performance and SEO
Features
1. App Router Structure
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: { default: 'My App', template: '%s | My App' },
description: 'A Next.js application',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Header />
<main>{children}</main>
<Footer />
</body>
</html>
);
}
import { Suspense } from 'react';
export default function HomePage() {
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold">Featured Products</h1>
<Suspense fallback={<ProductListSkeleton />}>
<ProductList />
</Suspense>
</div>
);
}
import { notFound } from 'next/navigation';
interface ProductPageProps {
params: { id: string };
}
export async function generateMetadata({ params }: ProductPageProps) {
const product = await getProduct(params.id);
if (!product) return { title: 'Not Found' };
return { title: product.name, description: product.description };
}
export default async function ProductPage({ params }: ProductPageProps) {
const product = await getProduct(params.id);
if (!product) notFound();
return <ProductDetails product={product} />;
}
2. Server and Client Components
import { getProducts } from '@/lib/products';
export async function ProductList() {
const products = await getProducts();
return (
<div className="grid grid-cols-3 gap-6">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
'use client';
import { useState, useTransition } from 'react';
import { addToCart } from '@/lib/actions';
export function AddToCartButton({ productId }: { productId: string }) {
const [isPending, startTransition] = useTransition();
const [message, setMessage] = useState<string | null>(null);
const handleClick = () => {
startTransition( () => {
result = (productId);
(result.);
});
};
(
);
}
3. Server Actions
'use server';
import { revalidatePath, revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
import { z } from 'zod';
import { auth } from '@/lib/auth';
const createProductSchema = z.object({
name: z.string().min(1),
price: z.number().positive(),
description: z.string().optional(),
});
export async function createProduct(formData: FormData) {
const session = await auth();
if (!session?.user?.id) throw new Error('Unauthorized');
const validatedFields = createProductSchema.safeParse({
name: formData.get('name'),
price: Number(formData.get('price')),
: formData.(),
});
(!validatedFields.) {
{ : , : validatedFields..(). };
}
product = db..({
: { ...validatedFields., : session.. },
});
();
();
}
() {
session = ();
(!session?.?.) {
{ : , : };
}
db..({
: { : { : session.., productId } },
: { : { : } },
: { : session.., productId, : },
});
();
{ : , : };
}
4. Data Fetching and Caching
import { unstable_cache } from 'next/cache';
export const getProducts = unstable_cache(
async () => {
return db.product.findMany({
include: { category: true },
orderBy: { createdAt: 'desc' },
});
},
['products'],
{ tags: ['products'], revalidate: 3600 }
);
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`, {
next: { revalidate: 3600 },
});
return res.json();
}
async function getCurrentPrice(symbol: string) {
const res = await fetch(`https://api.example.com/stocks/${symbol}`, {
: ,
});
res.();
}
() {
[product, relatedProducts, reviews] = .([
(id),
(id),
(id),
]);
{ product, relatedProducts, reviews };
}
5. API Routes
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const page = Number(searchParams.get('page') || '1');
const limit = Number(searchParams.get('limit') || '10');
const [products, total] = await Promise.all([
db.product.findMany({ skip: (page - 1) * limit, take: limit }),
db.product.count(),
]);
return NextResponse.json({
data: products,
pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
});
}
export async function () {
session = ();
(!session?.?.) {
.({ : }, { : });
}
body = request.();
product = db..({
: { ...body, : session.. },
});
.(product, { : });
}
() {
product = db..({ : { : params. } });
(!product) {
.({ : }, { : });
}
.(product);
}
6. Middleware
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { auth } from '@/lib/auth';
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const publicRoutes = ['/', '/login', '/register'];
if (publicRoutes.some((route) => pathname.startsWith(route))) {
return NextResponse.next();
}
const session = await auth();
if (!session) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('callbackUrl', pathname);
return NextResponse.redirect(loginUrl);
}
(pathname.() && session.. !== ) {
.( (, request.));
}
.();
}
config = {
: [],
};
7. Error Handling
'use client';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-2xl font-bold">Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
import Link from 'next/link';
export default function NotFound() {
return (
<div className="text-center py-12">
<h2 className="text-2xl font-bold">Page Not Found</h2>
<Link href="/">Return Home</Link>
</>
);
}
8. Performance Optimization
import Image from 'next/image';
export function OptimizedImage() {
return (
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority
placeholder="blur"
/>
);
}
import dynamic from 'next/dynamic';
const DynamicChart = dynamic(() => import('./Chart'), {
loading: () => <p>Loading...</p>,
ssr: false,
});
export async function generateStaticParams() {
const products = await getProducts();
return products.map((product) => ({ : product. }));
}
Use Cases
E-commerce Product Page
export default async function ProductPage({ params, searchParams }) {
const product = await getProductBySlug(params.slug);
if (!product) notFound();
const selectedVariant = product.variants.find(
(v) => v.id === searchParams.variant
) || product.variants[0];
return (
<div className="grid md:grid-cols-2 gap-8">
<ProductGallery images={product.images} />
<div>
<h1>{product.name}</h1>
<p>${selectedVariant.price}</p>
<VariantSelector variants={product.variants} />
<AddToCartButton productId={product.id} />
</div>
</div>
);
}
Best Practices
Do's
- Use Server Components by default
- Colocate data fetching with components
- Implement loading and error states
- Use Server Actions for mutations
- Cache data appropriately
- Optimize images with next/image
Don'ts
- Don't use 'use client' unnecessarily
- Don't fetch data in Client Components
- Don't ignore TypeScript errors
- Don't skip error boundaries
- Don't hardcode environment variables
References