The React Framework for production-grade applications with server-side rendering, static site generation, and full-stack capabilities
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
App Router (Default) - Recommended for new projects
app/Pages Router (Legacy) - Use for existing apps
pages/pages/apiDecision: New project? → App Router | Existing Pages app? → Continue with Pages
Use when:
Characteristics:
Use when:
Characteristics:
// Server Component (default)
export default async function Page() {
const data = await fetch('https://api.example.com/posts');
const posts = await data.json();
return <div>{JSON.stringify(posts)}</div>;
}
// Client Component
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState<number>(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}When: Content doesn't change often Performance: Cached at build time, served from CDN edge
export const revalidate = false; // Infinite cache
export default async function Page() {
const data = await fetch('https://api.example.com/posts');
return <div>{await data.json()}</div>;
}When: Content changes but doesn't need to be real-time Performance: Cached with periodic revalidation, mostly edge-served
export const revalidate = 60; // Revalidate every 60s
export default async function Page() {
const data = await fetch('https://api.example.com/posts');
return <div>{await data.json()}</div>;
}When: User-specific or frequently changing content Performance: Generated on every request, no caching
export const dynamic = 'force-dynamic';
export default async function Page() {
const data = await fetch('https://api.example.com/posts', {
cache: 'no-store'
});
return <div>{await data.json()}</div>;
}When: User interactions trigger data fetching Performance: Requires separate network request, adds latency
'use client';
import { useEffect, useState } from 'react';
interface SearchResult {
id: string;
title: string;
}
export default function Search() {
const [results, setResults] = useState<SearchResult[]>([]);
useEffect(() => {
fetch('/api/search')
.then(res => res.json())
.then((data: SearchResult[]) => setResults(data));
}, []);
return <div>{results.map(r => r.title).join(', ')}</div>;
}When: Want to show parts of page while others load Performance: Reduces Time to First Byte, improves perceived performance
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<SlowComponent />
</Suspense>
</div>
);
}Automatic - multiple identical requests are deduplicated
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // 1 hour
});// Fetch with tag
const posts = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] }
});
// Revalidate by tag
import { revalidateTag } from 'next/cache';
revalidateTag('posts');import { revalidatePath } from 'next/cache';
revalidatePath('/posts');app/
page.tsx → /
about/page.tsx → /about
blog/[slug]/page.tsx → /blog/[slug]
(marketing)/about/page.tsx → /about (route group)[id] → Single dynamic segment
[...slug] → Catch-all
[[...slug]] → Optional catch-all(marketing)/about/page.tsx → /about (parentheses not in URL)app/
layout.tsx
@analytics/
page.tsx → Analytics slot'use client';
import { useRouter, usePathname } from 'next/navigation';
const router = useRouter();
router.push('/about');import { redirect } from 'next/navigation';
export default async function Page() {
const user = await getUser();
if (!user) redirect('/login');
return <div>Content</div>;
}// app/error.tsx
'use client';
export default function Error({
error,
reset
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Error: {error.message}</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
// app/not-found.tsx
import { notFound } from 'next/navigation';
export default async function Post({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
const post = await getPost(id);
if (!post) notFound();
return <article>{post.content}</article>;
}import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Page Title',
description: 'Description',
openGraph: {
title: 'OG Title',
images: ['/og-image.jpg'],
},
};
// Dynamic
export async function generateMetadata({
params
}: {
params: Promise<{ id: string }>
}): Promise<Metadata> {
const { id } = await params;
const post = await getPost(id);
return { title: post.title };
}import Image from 'next/image';
<Image src="/photo.jpg" alt="Photo" width={800} height={600} priority />import { Inter } from 'next/font/google';
import type { ReactNode } from 'react';
const inter = Inter({ subsets: ['latin'], variable: '--font-inter' });
export default function Layout({ children }: { children: ReactNode }) {
return <html className={inter.variable}><body>{children}</body></html>;
}import Script from 'next/script';
<Script src="https://example.com/script.js" strategy="afterInteractive" />import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
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*' };