Next.js is the React framework that gives you production-ready features like server-side rendering, static site generation, and API routes out of the box.
If you're a full-stack developer building modern web applications, you've likely heard of Next.js. It has moved from a niche tool for static blogs to the default choice for many production React applications. I've used it to build everything from marketing sites to complex dashboards at suhailroushan.com. This guide cuts through the hype to show you where it shines, where it stumbles, and how to use it effectively.
Why Next.js Matters (and When to Skip It)
Next.js matters because it solves the fundamental tension between React's client-side dynamism and the web's need for speed and SEO. It bakes in solutions for routing, image optimization, and data fetching that you'd otherwise spend weeks configuring. The built-in API routes let you write backend logic alongside your frontend, collapsing the full-stack into a single, cohesive project.
You should skip it for two scenarios. First, if you're building a highly interactive, app-like experience (think Figma or a complex admin panel) where client-side routing and state are paramount, a pure React setup with Vite might be simpler. Second, if your project is a tiny, mostly static page, the framework's conventions add overhead for no tangible benefit.
Getting Started with Next.js
The fastest way to start is with the official create-next-app. Use the TypeScript template for type safety from day one.
npx create-next-app@latest my-app --typescript --tailwind --app
This command scaffolds a project with the modern App Router, TypeScript, and Tailwind CSS. The --app flag is crucial; it uses the new App Router paradigm, which is the future of Next.js. Your core application logic will live in an app/ directory.
Core Next.js Concepts Every Developer Should Know
Server Components vs. Client Components
This is the most important mental shift. By default, every component in the App Router is a Server Component. They render on the server, never ship JavaScript to the browser, and can directly access your database or APIs. You must explicitly use the 'use client' directive for interactivity.
// app/product-list.tsx
// This is a Server Component. It can fetch data directly.
import { db } from '@/lib/db';
export default async function ProductList() {
const products = await db.product.findMany(); // Direct database call
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
// app/add-to-cart-button.tsx
'use client'; // This directive makes it a Client Component
import { useState } from 'react';
export default function AddToCartButton({ productId }: { productId: string }) {
const [isLoading, setIsLoading] = useState(false);
const handleClick = async () => {
setIsLoading(true);
// Call a client-side API
await fetch('/api/cart', { method: 'POST', body: JSON.stringify({ productId }) });
setIsLoading(false);
};
return (
<button onClick={handleClick} disabled={isLoading}>
{isLoading ? 'Adding...' : 'Add to Cart'}
</button>
);
}
Defining API Routes
You can create backend endpoints inside your app directory by placing a route.ts file inside an api segment. Next.js handles the routing.
// app/api/cart/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { updateCart } from '@/lib/cart';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
await updateCart(body.productId);
return NextResponse.json({ success: true }, { status: 200 });
} catch (error) {
return NextResponse.json({ error: 'Update failed' }, { status: 500 });
}
}
Data Fetching Patterns
Use the native fetch() API with Next.js's enhanced caching and revalidation. The cache and next.revalidate options give you fine-grained control over static and dynamic behavior.
// Inside a Server Component or Server Action
const res = await fetch('https://api.example.com/data', {
cache: 'no-store', // Dynamic, fetch on every request
// or
next: { revalidate: 60 }, // Revalidate every 60 seconds (Incremental Static Regeneration)
});
Common Next.js Mistakes and How to Fix Them
-
Mistake: Using
useEffectfor initial data fetch in Server Components. Server Components are async and should fetch data directly. You cannot use hooks in them.- Fix: Fetch data directly in the Server Component. Move stateful logic to a Client Component if needed.
-
Mistake: Not specifying caching behavior. The default
fetchcache can lead to stale data or unnecessary refetches.- Fix: Always consciously set the
cacheornext.revalidateoption in yourfetchcalls based on your data's freshness requirements.
- Fix: Always consciously set the
-
Mistake: Placing
'use client'at the root of deeply nested component trees. This unnecessarily bundles server-safe code into the client bundle.- Fix: Push the
'use client'directive down as far as possible. Keep parent layouts and data-fetching components as Server Components, and only make leaf components that need interactivity into Client Components.
- Fix: Push the
When Should You Use Next.js?
Use Next.js when your project needs a blend of dynamic content and excellent core web vitals. It's the ideal choice for marketing websites with blogs, e-commerce product pages, dashboards that require SEO, and applications where you want to colocate frontend and API logic. Choose it when you value convention over configuration for routing, images, and performance optimizations.
Next.js in Production
First, always use the next start command for production deployment, not next dev. Configure your environment variables properly using .env.local for secrets and .env for public variables. Second, leverage Incremental Static Regeneration (ISR) aggressively. It allows you to update static content without a full rebuild. For example, a product page can be statically generated at build time and then revalidated every 60 seconds if the price changes.
Start your next project by consciously deciding which parts of your page are Server Components and which need the 'use client' directive—this single decision will define your app's performance and architecture.