Next.js Syntax Cheat Sheet

File Conventions

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

// ✅ Correct naming
page.js          // Route pages
layout.js        // Layouts
loading.js       // Loading states
error.js         // Error boundaries
not-found.js     // 404 pages
route.js         // API routes

// ❌ Avoid these names
index.js         // Use page.js instead
default.js       // Use page.js instead

Component Syntax

Basic Component

// app/components/Button.jsx
export default function Button({ children, onClick, variant = 'primary' }) {
    return (
        <button 
            onClick={onClick}
            className={`btn btn-${variant}`}
        >
            {children}
        </button>
    );
}

Server Component (Default)

// app/components/ServerComponent.jsx
import { getData } from '@/lib/data';

export default async function ServerComponent() {
    const data = await getData(); // Can use async/await
    
    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

// app/layout.js
export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <body>
                <header>
                    <nav>Navigation</nav>
                </header>
                
                <main>{children}</main>
                
                <footer>
                    <p>&copy; 2024</p>
                </footer>
            </body>
        </html>
    );
}

Page Components

Basic Page

// app/page.js
export default function HomePage() {
    return (
        <div>
            <h1>Welcome to Next.js</h1>
            <p>This is the home page</p>
        </div>
    );
}

Dynamic Page

// app/blog/[slug]/page.js
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

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

// app/search/page.js
export default function SearchPage({ searchParams }) {
    const query = searchParams.q || '';
    
    return (
        <div>
            <h1>Search Results</h1>
            <p>Searching for: {query}</p>
        </div>
    );
}

JSX Rules and Patterns

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>
    );
}

Hooks Usage

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();
    }, []); // Empty dependency array = run once
    
    if (loading) return <div>Loading...</div>;
    
    return <div>{JSON.stringify(data)}</div>;
}

Custom Hooks

// hooks/useLocalStorage.js
'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];
}

// Usage
export default function ThemeToggle() {
    const [theme, setTheme] = useLocalStorage('theme', 'light');
    
    return (
        <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
            Current theme: {theme}
        </button>
    );
}

Data Fetching

Server-Side Fetching

// app/posts/page.js
export default async function PostsPage() {
    const posts = await fetch('https://api.example.com/posts', {
        next: { revalidate: 3600 } // Cache for 1 hour
    }).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>;
}

API Routes

Basic API Route

// app/api/hello/route.js
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

// app/api/users/[id]/route.js
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 Handling

Error Boundary

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

// app/loading.js
export default function Loading() {
    return (
        <div className="loading">
            <div className="spinner"></div>
            <p>Loading...</p>
        </div>
    );
}

Not Found Page

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

Styling Patterns

CSS Modules

// app/components/Button.module.css
.button {
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.primary {
    background-color: #007bff;
    color: white;
}

.secondary {
    background-color: #6c757d;
    color: white;
}

// Button.jsx
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>
    );
}

Best Practices

Component Organization

// ✅ Good structure
export default function UserProfile({ user }) {
    // 1. Hooks
    const [isEditing, setIsEditing] = useState(false);
    
    // 2. Event handlers
    const handleEdit = () => setIsEditing(true);
    const handleSave = () => setIsEditing(false);
    
    // 3. Computed values
    const displayName = user.displayName || user.email;
    
    // 4. Render
    return (
        <div>
            <h1>{displayName}</h1>
            {isEditing ? (
                <EditForm user={user} onSave={handleSave} />
            ) : (
                <UserInfo user={user} onEdit={handleEdit} />
            )}
        </div>
    );
}

Props Destructuring

// ✅ Good
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>
    );
}

// ❌ Avoid
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';

// Memoized component
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
    return <div>{/* Expensive rendering */}</div>;
});

// Memoized callback
export default function ParentComponent({ items }) {
    const handleClick = useCallback((id) => {
        console.log('Clicked:', id);
    }, []);
    
    // Memoized computation
    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

// types/user.ts
export interface User {
    id: string;
    name: string;
    email: string;
    avatar?: string;
}

// components/UserProfile.tsx
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>
    );
}