App Router Structure
app/
├── layout.js # Root layout (required)
├── page.js # Home page (/)
├── loading.js # Loading UI
├── error.js # Error UI
├── not-found.js # 404 page
├── globals.css # Global styles
├── [slug]/
│ └── page.js # Dynamic route
└── api/
└── route.js # API endpoint
File Naming Conventions
page.js
layout.js
loading.js
error.js
not-found.js
route.js
index.js
default.js
Basic Component
export default function Button({ children, onClick, variant = 'primary' }) {
return (
<button
onClick={onClick}
className={`btn btn-${variant}`}
>
{children}
</button>
);
}
Server Component (Default)
import { getData } from '@/lib/data';
export default async function ServerComponent() {
const data = await getData();
return (
<div>
<h1>Server Component</h1>
<p>{data.message}</p>
</div>
);
}
Client Component
'use client';
import { useState, useEffect } from 'react';
export default function ClientComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Component mounted');
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Layout Component
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header>
<nav>Navigation</nav>
</header>
<main>{children}</main>
<footer>
<p>© 2024</p>
</footer>
</body>
</html>
);
}
Page Components
Basic Page
export default function HomePage() {
return (
<div>
<h1>Welcome to Next.js</h1>
<p>This is the home page</p>
</div>
);
}
Dynamic Page
export default async function BlogPost({ params }) {
const { slug } = params;
return (
<article>
<h1>Blog Post: {slug}</h1>
<p>Content for {slug}</p>
</article>
);
}
Page with Metadata
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
};
export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>Company information</p>
</div>
);
}
Page with Search Params
export default function SearchPage({ searchParams }) {
const query = searchParams.q || '';
return (
<div>
<h1>Search Results</h1>
<p>Searching for: {query}</p>
</div>
);
}
Conditional Rendering
export default function ConditionalComponent({ user, isLoading }) {
if (isLoading) {
return <div>Loading...</div>;
}
return (
<div>
{user ? (
<div>Welcome, {user.name}!</div>
) : (
<div>Please log in</div>
)}
{/* Inline conditional */}
{user && <div>User dashboard</div>}
{!user && <div>Login form</div>}
</div>
);
}
List Rendering
export default function ListComponent({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={item.id || index}>
{item.name}
</li>
))}
</ul>
);
}
Fragment Usage
export default function FragmentComponent() {
return (
<>
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</>
);
}
Spread Operator
export default function SpreadComponent({ title, ...props }) {
return (
<div {...props}>
<h1>{title}</h1>
{/* props contains all other properties */}
</div>
);
}
useState Hook
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', email: '' });
const increment = () => setCount(prev => prev + 1);
const updateUser = (field, value) => {
setUser(prev => ({ ...prev, [field]: value }));
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<input
value={user.name}
onChange={(e) => updateUser('name', e.target.value)}
placeholder="Name"
/>
</div>
);
}
useEffect Hook
'use client';
import { useState, useEffect } from 'react';
export default function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
return <div>{JSON.stringify(data)}</div>;
}
Custom Hooks
'use client';
import { useState, useEffect } from 'react';
export function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
}
return initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
export default function ThemeToggle() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
}
Server-Side Fetching
export default async function PostsPage() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
}).then(res => res.json());
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
Client-Side Fetching
'use client';
import { useState, useEffect } from 'react';
export default function ClientDataFetcher() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData)
.catch(setError);
}, []);
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>Loading...</div>;
return <div>{JSON.stringify(data)}</div>;
}
Basic API Route
export async function GET() {
return Response.json({ message: 'Hello World' });
}
export async function POST(request) {
const data = await request.json();
return Response.json({ received: data });
}
API Route with Parameters
export async function GET(request, { params }) {
const { id } = params;
const user = await getUser(id);
if (!user) {
return Response.json({ error: 'User not found' }, { status: 404 });
}
return Response.json(user);
}
Error Boundary
'use client';
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}
Loading State
export default function Loading() {
return (
<div className="loading">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
}
Not Found Page
import Link from 'next/link';
export default function NotFound() {
return (
<div>
<h2>Page Not Found</h2>
<p>Could not find the requested resource.</p>
<Link href="/">Go back home</Link>
</div>
);
}
CSS Modules
.button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.primary {
background-color: #007bff;
color: white;
}
.secondary {
background-color: #6c757d;
color: white;
}
import styles from './Button.module.css';
export default function Button({ children, variant = 'primary' }) {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);
}
Tailwind CSS
export default function TailwindComponent() {
return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-3xl font-bold text-gray-900 mb-4">
Title
</h1>
<p className="text-gray-600 leading-relaxed">
Content with Tailwind classes
</p>
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Button
</button>
</div>
);
}
Inline Styles
export default function InlineStyleComponent() {
const styles = {
container: {
maxWidth: '1200px',
margin: '0 auto',
padding: '20px',
},
title: {
fontSize: '2rem',
fontWeight: 'bold',
color: '#333',
},
};
return (
<div style={styles.container}>
<h1 style={styles.title}>Title</h1>
</div>
);
}
Component Organization
export default function UserProfile({ user }) {
const [isEditing, setIsEditing] = useState(false);
const handleEdit = () => setIsEditing(true);
const handleSave = () => setIsEditing(false);
const displayName = user.displayName || user.email;
return (
<div>
<h1>{displayName}</h1>
{isEditing ? (
<EditForm user={user} onSave={handleSave} />
) : (
<UserInfo user={user} onEdit={handleEdit} />
)}
</div>
);
}
Props Destructuring
export default function UserCard({ user, onEdit, onDelete, ...props }) {
return (
<div {...props}>
<h2>{user.name}</h2>
<button onClick={() => onEdit(user.id)}>Edit</button>
<button onClick={() => onDelete(user.id)}>Delete</button>
</div>
);
}
export default function UserCard(props) {
return (
<div>
<h2>{props.user.name}</h2>
<button onClick={() => props.onEdit(props.user.id)}>Edit</button>
</div>
);
}
Conditional Classes
import clsx from 'clsx';
export default function Button({ variant, size, disabled, children }) {
const buttonClasses = clsx(
'btn',
{
'btn-primary': variant === 'primary',
'btn-secondary': variant === 'secondary',
'btn-sm': size === 'small',
'btn-lg': size === 'large',
'opacity-50 cursor-not-allowed': disabled,
}
);
return (
<button className={buttonClasses} disabled={disabled}>
{children}
</button>
);
}
Performance Optimization
'use client';
import { memo, useCallback, useMemo } from 'react';
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
return <div>{/* Expensive rendering */}</div>;
});
export default function ParentComponent({ items }) {
const handleClick = useCallback((id) => {
console.log('Clicked:', id);
}, []);
const processedItems = useMemo(() => {
return items.map(item => ({
...item,
processed: item.value * 2,
}));
}, [items]);
return (
<div>
{processedItems.map(item => (
<ExpensiveComponent
key={item.id}
data={item}
onClick={handleClick}
/>
))}
</div>
);
}
TypeScript Integration
export interface User {
id: string;
name: string;
email: string;
avatar?: string;
}
import { User } from '@/types/user';
interface UserProfileProps {
user: User;
onEdit?: (user: User) => void;
onDelete?: (id: string) => void;
}
export default function UserProfile({ user, onEdit, onDelete }: UserProfileProps) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
{user.avatar && <img src={user.avatar} alt={user.name} />}
</div>
);
}