Next.js Components Cheat Sheet

Next.js Components Cheatsheet

Server vs Client Components

Server Components (Default)

// app/components/ServerComponent.js
// Server Components run on the server by default
async function ServerComponent() {
    // Can use async/await
    const data = await fetch('https://api.example.com/data');
    const posts = await data.json();
    
    // Can access server-side APIs
    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

// app/components/ClientComponent.js
'use client'; // Must be first line

import { useState, useEffect } from 'react';

function ClientComponent() {
    const [count, setCount] = useState(0);
    const [data, setData] = useState(null);
    
    useEffect(() => {
        // Can use browser APIs
        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

// app/page.js
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>
    );
}

Component Patterns

Layout Components

// app/components/Layout.js
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>
    );
}

// app/layout.js
import Layout from './components/Layout';

export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <body>
                <Layout>
                    {children}
                </Layout>
            </body>
        </html>
    );
}

Container Components

// app/components/Container.js
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>
    );
}

// Usage
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)

// app/components/withAuth.js
'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} />;
    };
}

// Usage
function ProtectedPage({ session }) {
    return <div>Welcome, {session.user.name}!</div>;
}

export default withAuth(ProtectedPage);

Props and Data Flow

Props Handling

// app/components/UserCard.js
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>
    );
}

// Usage
<UserCard 
    user={user} 
    showEmail={true}
    onEdit={(id) => handleEdit(id)}
>
    <p>Additional content</p>
</UserCard>

Server Component Props

// app/components/PostList.js
async function PostList({ category, limit = 10 }) {
    // Props are serializable in server components
    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>
    );
}

// app/page.js
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

// app/components/Counter.js
'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>
    );
}

Rendering Strategies

Static Rendering

// app/components/StaticComponent.js
// Rendered at build time
export default function StaticComponent() {
    return (
        <div>
            <h1>Static Content</h1>
            <p>This content is rendered at build time.</p>
        </div>
    );
}

// app/page.js
export default function Page() {
    return (
        <div>
            <StaticComponent />
        </div>
    );
}

Dynamic Rendering

// app/components/DynamicComponent.js
// Rendered on each request
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

// app/components/StreamingComponent.js
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>
    );
}

Performance Optimization

Memoization

// app/components/ExpensiveComponent.js
'use client';

import { memo, useMemo, useCallback } from 'react';

const ExpensiveComponent = memo(function ExpensiveComponent({ data, onUpdate }) {
    // Memoize expensive calculations
    const processedData = useMemo(() => {
        return data.map(item => ({
            ...item,
            processed: item.value * 2
        }));
    }, [data]);
    
    // Memoize callbacks
    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

// app/components/LazyComponent.js
import dynamic from 'next/dynamic';

// Lazy load heavy components
const HeavyChart = dynamic(() => import('./HeavyChart'), {
    loading: () => <div>Loading chart...</div>,
    ssr: false // Disable SSR for client-only components
});

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

// app/components/FeatureComponent.js
import dynamic from 'next/dynamic';

// Split by feature
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 Handling

Error Boundaries

// app/components/ErrorBoundary.js
'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

// app/components/ErrorComponent.js
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>
    );
}

// app/error.js
'use client';

import ErrorComponent from './components/ErrorComponent';

export default function Error({ error, reset }) {
    return <ErrorComponent error={error} reset={reset} />;
}

Form Components

Server Actions

// app/components/ContactForm.js
import { useFormState } from 'react-dom';

async function submitForm(prevState, formData) {
    'use server';
    
    const name = formData.get('name');
    const email = formData.get('email');
    
    // Validate and process form data
    if (!name || !email) {
        return { error: 'All fields are required' };
    }
    
    // Save to database
    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

// app/components/ClientForm.js
'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>
    );
}

Best Practices

Component Organization

// Recommended file structure
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

// Component naming
// ✅ Good
export default function UserProfile() { }
export default function PostCard() { }
export default function ContactForm() { }

// ❌ Bad
export default function userProfile() { }
export default function post_card() { }
export default function Contactform() { }

// File naming
// ✅ Good
UserProfile.js
PostCard.js
ContactForm.js

// ❌ Bad
userProfile.js
post_card.js
contactform.js

Performance Tips

// 1. Use Server Components by default
// 2. Only add 'use client' when necessary
// 3. Memoize expensive calculations
// 4. Use dynamic imports for large components
// 5. Implement proper error boundaries
// 6. Use Suspense for loading states
// 7. Optimize re-renders with memo and useMemo
// 8. Keep components focused and single-purpose