Next.js Components Cheatsheet
Server Components (Default)
async function ServerComponent() {
const data = await fetch('https://api.example.com/data');
const posts = await data.json();
const db = await connectToDatabase();
const users = await db.query('SELECT * FROM users');
return (
<div>
<h1>Server Component</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
export default ServerComponent;
Client Components
'use client';
import { useState, useEffect } from 'react';
function ClientComponent() {
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
};
fetchData();
}, []);
return (
<div>
<h2>Client Component</h2>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}
export default ClientComponent;
Component Boundaries
import ServerComponent from './components/ServerComponent';
import ClientComponent from './components/ClientComponent';
export default function Page() {
return (
<div>
{/* Server Component - rendered on server */}
<ServerComponent />
{/* Client Component - rendered on client */}
<ClientComponent />
{/* Server Component with client component inside */}
<ServerComponent>
<ClientComponent />
</ServerComponent>
</div>
);
}
Layout Components
import Header from './Header';
import Footer from './Footer';
export default function Layout({ children }) {
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1">
{children}
</main>
<Footer />
</div>
);
}
import Layout from './components/Layout';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Layout>
{children}
</Layout>
</body>
</html>
);
}
Container Components
export default function Container({ children, className = '' }) {
return (
<div className={`max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 ${className}`}>
{children}
</div>
);
}
import Container from './components/Container';
export default function Page() {
return (
<Container>
<h1>Page Content</h1>
<p>This content is centered and responsive.</p>
</Container>
);
}
Higher-Order Components (HOCs)
'use client';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
export function withAuth(Component) {
return function AuthenticatedComponent(props) {
const { data: session, status } = useSession();
const router = useRouter();
if (status === 'loading') {
return <div>Loading...</div>;
}
if (!session) {
router.push('/login');
return null;
}
return <Component {...props} session={session} />;
};
}
function ProtectedPage({ session }) {
return <div>Welcome, {session.user.name}!</div>;
}
export default withAuth(ProtectedPage);
Props Handling
export default function UserCard({
user,
showEmail = false,
onEdit,
children
}) {
return (
<div className="border rounded-lg p-4">
<h3 className="text-lg font-semibold">{user.name}</h3>
{showEmail && <p className="text-gray-600">{user.email}</p>}
{onEdit && (
<button
onClick={() => onEdit(user.id)}
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
>
Edit
</button>
)}
{children}
</div>
);
}
<UserCard
user={user}
showEmail={true}
onEdit={(id) => handleEdit(id)}
>
<p>Additional content</p>
</UserCard>
Server Component Props
async function PostList({ category, limit = 10 }) {
const posts = await fetch(`/api/posts?category=${category}&limit=${limit}`);
const data = await posts.json();
return (
<div>
{data.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
);
}
export default async function Page({ searchParams }) {
const category = searchParams.category || 'all';
return (
<div>
<h1>Posts</h1>
<PostList category={category} limit={20} />
</div>
);
}
Client Component Props
'use client';
import { useState } from 'react';
export default function Counter({ initialValue = 0, step = 1 }) {
const [count, setCount] = useState(initialValue);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + step)}>
Increment by {step}
</button>
<button onClick={() => setCount(count - step)}>
Decrement by {step}
</button>
</div>
);
}
Static Rendering
export default function StaticComponent() {
return (
<div>
<h1>Static Content</h1>
<p>This content is rendered at build time.</p>
</div>
);
}
export default function Page() {
return (
<div>
<StaticComponent />
</div>
);
}
Dynamic Rendering
export const dynamic = 'force-dynamic';
export default async function DynamicComponent() {
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
const result = await data.json();
return (
<div>
<h1>Dynamic Content</h1>
<p>Last updated: {new Date().toLocaleString()}</p>
<pre>{JSON.stringify(result, null, 2)}</pre>
</div>
);
}
Streaming Components
import { Suspense } from 'react';
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000));
return <div>Slow content loaded!</div>;
}
export default function StreamingComponent() {
return (
<div>
<h1>Streaming Example</h1>
<Suspense fallback={<div>Loading slow content...</div>}>
<SlowComponent />
</Suspense>
</div>
);
}
Memoization
'use client';
import { memo, useMemo, useCallback } from 'react';
const ExpensiveComponent = memo(function ExpensiveComponent({ data, onUpdate }) {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
processed: item.value * 2
}));
}, [data]);
const handleClick = useCallback((id) => {
onUpdate(id);
}, [onUpdate]);
return (
<div>
{processedData.map(item => (
<div key={item.id} onClick={() => handleClick(item.id)}>
{item.name}: {item.processed}
</div>
))}
</div>
);
});
export default ExpensiveComponent;
Lazy Loading
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <div>Loading chart...</div>,
ssr: false
});
const HeavyTable = dynamic(() => import('./HeavyTable'), {
loading: () => <div>Loading table...</div>
});
export default function LazyComponent() {
return (
<div>
<h1>Lazy Loaded Components</h1>
<HeavyChart />
<HeavyTable />
</div>
);
}
Code Splitting
import dynamic from 'next/dynamic';
const AdminPanel = dynamic(() => import('./AdminPanel'), {
loading: () => <div>Loading admin panel...</div>
});
const UserDashboard = dynamic(() => import('./UserDashboard'), {
loading: () => <div>Loading dashboard...</div>
});
export default function FeatureComponent({ userRole }) {
return (
<div>
{userRole === 'admin' ? <AdminPanel /> : <UserDashboard />}
</div>
);
}
Error Boundaries
'use client';
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong</h2>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Error Components
export default function ErrorComponent({ error, reset }) {
return (
<div className="error-container">
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}
'use client';
import ErrorComponent from './components/ErrorComponent';
export default function Error({ error, reset }) {
return <ErrorComponent error={error} reset={reset} />;
}
Server Actions
import { useFormState } from 'react-dom';
async function submitForm(prevState, formData) {
'use server';
const name = formData.get('name');
const email = formData.get('email');
if (!name || !email) {
return { error: 'All fields are required' };
}
await saveContact({ name, email });
return { success: 'Form submitted successfully!' };
}
export default function ContactForm() {
const [state, formAction] = useFormState(submitForm, {});
return (
<form action={formAction}>
<div>
<label htmlFor="name">Name:</label>
<input type="text" id="name" name="name" required />
</div>
<div>
<label htmlFor="email">Email:</label>
<input type="email" id="email" name="email" required />
</div>
<button type="submit">Submit</button>
{state.error && <p className="error">{state.error}</p>}
{state.success && <p className="success">{state.success}</p>}
</form>
);
}
Client Form Components
'use client';
import { useState } from 'react';
export default function ClientForm() {
const [formData, setFormData] = useState({
name: '',
email: ''
});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (response.ok) {
alert('Form submitted successfully!');
setFormData({ name: '', email: '' });
}
} catch (error) {
console.error('Error submitting form:', error);
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="Name"
required
/>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
placeholder="Email"
required
/>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}
Component Organization
app/
├── components/
│ ├── ui/ # Reusable UI components
│ │ ├── Button.js
│ │ ├── Input.js
│ │ └── Modal.js
│ ├── layout/ # Layout components
│ │ ├── Header.js
│ │ ├── Footer.js
│ │ └── Sidebar.js
│ ├── forms/ # Form components
│ │ ├── ContactForm.js
│ │ └── LoginForm.js
│ └── features/ # Feature-specific components
│ ├── PostCard.js
│ └── UserProfile.js
Naming Conventions
export default function UserProfile() { }
export default function PostCard() { }
export default function ContactForm() { }
export default function userProfile() { }
export default function post_card() { }
export default function Contactform() { }
UserProfile.js
PostCard.js
ContactForm.js
userProfile.js
post_card.js
contactform.js
Performance Tips