Next.js getServerSideProps — 12k req/s Server Crash
At 12k req/s, average response time jumped from 120ms to 8,400ms, CPU 100%.
20+ years shipping production JavaScript and front-end systems at scale. Everything here is grounded in real deployments.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- Next.js is a React framework that adds file-based routing, server rendering, and build-time optimisation
- File-based routing: drop a file in pages/ or app/ and the route is live — no router config needed
- SSG (getStaticProps) builds HTML at deploy time — fastest option, served from CDN
- SSR (getServerSideProps) builds HTML per request — use only when data must be fresh AND in initial HTML
- ISR adds a revalidate timer to SSG — static speed with near-real-time freshness
- Biggest mistake: using SSR everywhere for 'freshness' when ISR solves 90% of those cases faster
Next.js is React with a default build pipeline that bakes in routing, data fetching, and rendering strategies so you don't have to wire up Webpack yourself. React is a library for building UIs. Next.js is a framework that decides how those UIs get served to a browser.
The real distinction: React leaves you with a blank HTML file and a JavaScript bundle. Your users download the bundle, React renders the page. That works for dashboards. For public-facing sites where SEO and first paint matter, it's a disaster.
Next.js gives you SSR, SSG, ISR, and client-side rendering out of the box. You pick the strategy per page. It also handles code splitting, image optimization, and API routes. It's not magic — it's opinionated defaults that save you from yourself.
If you're shipping a production app that needs to load fast for real users on poor connections, you don't want to configure Babel and Webpack. You want Next.js. Period.
Think of a regular React app like a food truck where every customer has to wait while the chef cooks their meal from scratch. Next.js is like a restaurant that pre-cooks popular dishes during quiet hours, so when you sit down, your food arrives almost instantly. For less popular dishes, it still cooks fresh — but it knows the difference. That's the core idea: Next.js decides the smartest way to serve each page of your app.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Next.js solves three problems that plain React cannot: initial page load performance, search engine visibility, and routing complexity. A standard React app ships a blank HTML shell and waits for JavaScript to hydrate — users see a spinner, crawlers see nothing. Next.js renders pages on the server or at build time, so users and search engines get real HTML immediately.
The framework supports four rendering strategies in a single project: Static Site Generation (SSG), Server-Side Rendering (SSR), Client-Side Rendering (CSR), and Incremental Static Regeneration (ISR). Choosing the right strategy per page is the core skill that separates a competent Next.js developer from one who ships slow, expensive applications.
Why getServerSideProps Is Not Your SSR Friend
getServerSideProps is a Next.js function that runs on every request to fetch data at server-side render time. It blocks the response until the async function resolves, meaning the server's event loop is occupied for the duration of the fetch. This is fundamentally different from static generation (getStaticProps) or client-side fetching — it trades caching for freshness, but at a direct cost to request throughput.
Each invocation creates a new Node.js promise chain. If your data source (e.g., a database or external API) has a 200ms latency, a single server process handling 12,000 requests per second will have 2,400 concurrent in-flight promises. Node.js can handle that, but the real bottleneck is the downstream service: a 200ms query under 2,400 concurrent calls often degrades to 2–5 seconds, causing cascading timeouts. The server's connection pool, memory, and CPU all spike linearly with concurrency.
Use getServerSideProps only when you absolutely need per-request data that cannot be cached or pre-rendered — for example, user-specific dashboards behind authentication. For any page where data changes infrequently, prefer Incremental Static Regeneration (ISR) or client-side fetching with a loading state. In high-traffic systems, getServerSideProps is often the first thing to kill your server under load.
File-Based Routing: Why Your Folder Structure IS Your App's Navigation
In a standard React app, you manually wire up React Router — you import BrowserRouter, define Route components, and manage path strings in your head. It works, but it's boilerplate you didn't need to write. Next.js takes a different philosophy: your file system is your router. Drop a file called about.js inside the pages folder and /about becomes a live route instantly. No imports. No configuration. No forgetting to register a new page.
This isn't just a convenience trick. It's an architectural choice that enforces consistency across teams. When a new developer joins your project, they don't need to read routing config — they just look at the folder structure. Files are truth.
Dynamic routes use square-bracket syntax. A file named [productId].js matches /products/42, /products/shoes, or any slug you throw at it. The matched value lands in useRouter().query so you can fetch the right data. This pattern covers 90% of real-world routing needs — product pages, blog posts, user profiles — without a single line of router config.
// File location: pages/products/[productId].js // This single file handles EVERY product URL: /products/1, /products/42, etc. import { useRouter } from 'next/router'; export default function ProductPage() { // useRouter gives us access to the URL parameters const router = useRouter(); // productId comes from the filename [productId].js // On the URL /products/42, this will be the string '42' const { productId } = router.query; // During the initial render on the client, query can be empty // so we guard against that with a loading state if (!productId) { return <p>Loading product...</p>; } return ( <div> <h1>Product Details</h1> {/* Shows the dynamic segment from the URL */} <p>You are viewing product ID: {productId}</p> <p> This page was generated by the file: pages/products/[productId].js </p> </div> ); } // --- FOLDER STRUCTURE REFERENCE --- // pages/ // index.js → renders at / // about.js → renders at /about // products/ // index.js → renders at /products // [productId].js → renders at /products/:productId // blog/ // [slug].js → renders at /blog/:slug
router.query starts as an empty object {} before populating with the actual URL values. Always guard with if (!productId) return null or a loading state — otherwise you'll pass undefined to your API calls and wonder why nothing works.SSG vs SSR vs CSR: Choosing the Right Data Fetching Strategy
This is where Next.js earns its keep — and where most developers get confused because they treat all three strategies as interchangeable. They're not. Each one answers a different question about WHEN data should be fetched and WHO should do the fetching.
Static Site Generation (SSG) with getStaticProps runs at build time on the server. The HTML is generated once and served from a CDN forever. This is perfect for content that doesn't change often — a product catalogue, a blog post, a pricing page. It's the fastest option because there's no server work happening per request.
Server-Side Rendering (SSR) with getServerSideProps runs on every single request. The server fetches fresh data and builds the HTML before sending it to the user. Use this only when the data must be up-to-the-second fresh AND you need it in the initial HTML — think a live dashboard or a personalised feed that depends on the user's session cookie.
Client-Side Rendering (CSR) is just regular React — the page loads, then JavaScript fetches data in a useEffect. Use this for data that's behind authentication and doesn't need to be indexed by search engines, like user account settings. Mixing all three in the same app based on each page's needs is the Next.js superpower.
// File: pages/blog/[slug].js // This page uses SSG — it's pre-built at deploy time // Perfect for blog posts that don't change every minute export default function BlogPost({ post }) { // 'post' comes from getStaticProps below — it's already resolved // by the time this component renders. No loading state needed! return ( <article> <h1>{post.title}</h1> <p>By {post.author} on {post.publishedDate}</p> <div>{post.content}</div> </article> ); } // getStaticPaths tells Next.js WHICH URLs to pre-build // This runs at build time — not on every request export async function getStaticPaths() { // In real life, fetch this list from your CMS or database const publishedSlugs = ['intro-to-nextjs', 'react-hooks-explained', 'css-grid-guide']; const paths = publishedSlugs.map((slug) => ({ params: { slug }, // must match the [slug] in your filename })); return { paths, // fallback: false means unknown slugs return a 404 // fallback: 'blocking' means unknown slugs trigger SSR on first hit, then cache fallback: false, }; } // getStaticProps receives the slug and fetches that post's data // Runs ONCE per slug at build time — never again (until revalidate triggers) export async function getStaticProps({ params }) { // Simulating a CMS or database fetch using the slug from the URL const response = await fetch(`https://api.example.com/posts/${params.slug}`); const post = await response.json(); return { props: { post }, // injected as props into BlogPost component above // revalidate: 60 means re-generate this page in the background // if a request comes in after 60 seconds — this is called ISR revalidate: 60, }; } // --- WHEN TO USE WHAT --- // getStaticProps → blog posts, product pages, marketing pages (fast, SEO-friendly) // getServerSideProps → user dashboards, pages needing live data or session cookies // useEffect fetch → post-login UI, private account pages, non-SEO content
revalidate: N to your getStaticProps return — gives you static speed with near-real-time freshness. The page serves from cache until N seconds pass, then regenerates in the background on the next visit. Most e-commerce and content sites should default to ISR over full SSR.The Next.js Link and Image Components: Not Just Wrappers
Two components trip up almost every developer coming from plain React: next/link and next/image. They look like simple wrappers around an anchor tag and an img tag, but they're doing serious work under the hood — work that would take hours to implement manually.
next/link enables client-side navigation. When you use it, clicking a link doesn't trigger a full browser reload — Next.js swaps the page in JavaScript, keeping your app state alive. But here's the part that actually matters: Next.js automatically prefetches linked pages in the background when they appear in the viewport. The user moves their mouse toward a link and the destination is already loading. This is why Next.js apps feel snappy — it's not magic, it's strategic prefetching.
next/image solves one of the biggest real-world performance problems: images. It automatically resizes images to the right dimensions for the user's device, serves modern formats like WebP when the browser supports it, lazy-loads images below the fold by default, and prevents layout shift by requiring width and height props. The priority prop tells it to load a specific image eagerly — use that on your above-the-fold hero image to nail your Core Web Vitals score.
// File: components/ProductCard.js // Demonstrates real-world usage of next/link and next/image import Link from 'next/link'; import Image from 'next/image'; export default function ProductCard({ product }) { return ( <div className="product-card"> {/* next/image vs plain <img>: - Automatically serves WebP format if browser supports it - Resizes to the rendered size — not the original 4000px source - Lazy loads by default (doesn't load until near viewport) - Prevents CLS (Cumulative Layout Shift) via the width/height aspect ratio */} <Image src={product.imageUrl} alt={product.name} // alt is required — good for accessibility AND SEO width={400} // intrinsic width of the image height={300} // intrinsic height — sets the aspect ratio // Add priority={true} ONLY for above-the-fold images like hero banners // priority={true} tells Next.js to preload it, skipping lazy loading /> <h2>{product.name}</h2> <p>${product.price}</p> {/* next/link vs plain <a>: - Client-side navigation — no full page reload - Prefetches the linked page HTML+JS in the background when visible - Falls back to normal <a> behaviour if JavaScript hasn't loaded yet */} <Link href={`/products/${product.id}`}> {/* As of Next.js 13+, you don't need a nested <a> tag anymore */} View Product Details </Link> </div> ); } // Usage in a parent page: // <ProductCard // product={{ // id: 42, // name: 'Trail Running Shoes', // price: 129.99, // imageUrl: '/images/trail-shoes.jpg' // }} // />
The App Router vs Pages Router: What Changed in Next.js 13+
If you've been reading Next.js docs and feeling confused by two completely different patterns, here's the honest explanation: Next.js 13 introduced a brand-new routing system called the App Router, and both systems now coexist in the same framework. The older system is the Pages Router (everything inside a pages/ folder, which is what this article has covered so far). The App Router lives inside an app/ folder and uses a different set of conventions.
The App Router is built around React Server Components — a newer React feature that lets components run only on the server, with zero JavaScript shipped to the browser. In the App Router, every component is a Server Component by default. You opt into client-side interactivity by adding 'use client' at the top of a file.
The data fetching story also changes: instead of getStaticProps and getServerSideProps, you just use async/await directly inside Server Components and call with caching options. It's more flexible but requires a mental shift.fetch()
For new production projects starting today, learn the App Router. For jobs and existing codebases, the Pages Router is still everywhere — understanding both is what separates a competent Next.js developer from a great one.
// File: app/products/[productId]/page.js (App Router convention) // Notice: no getStaticProps, no useRouter for data — just async/await directly // This is a SERVER COMPONENT by default. // It runs only on the server. Zero JS is sent to the browser for this component. // That means: faster page loads, direct database access, no API layer needed. export default async function ProductPage({ params }) { // params comes from the folder name [productId] — same concept as Pages Router const { productId } = params; // fetch() in Server Components is enhanced by Next.js // next: { revalidate: 3600 } is equivalent to ISR — cache for 1 hour const response = await fetch( `https://api.example.com/products/${productId}`, { next: { revalidate: 3600 } } // ISR-style caching built right into fetch ); const product = await response.json(); return ( <div> <h1>{product.name}</h1> <p>{product.description}</p> <p>Price: ${product.price}</p> {/* AddToCartButton needs onClick — that's client-side behaviour. We mark it 'use client' in its own file and import it here. The Server Component passes data down; the Client Component handles interaction. */} <AddToCartButton productId={productId} /> </div> ); } // --- app/components/AddToCartButton.js --- // 'use client' at the top opts this file into client-side rendering // Only components that NEED interactivity should be client components 'use client'; import { useState } from 'react'; export function AddToCartButton({ productId }) { const [added, setAdded] = useState(false); function handleClick() { // In real life: call your cart API here console.log(`Adding product ${productId} to cart`); setAdded(true); } return ( <button onClick={handleClick}> {added ? 'Added to Cart ✓' : 'Add to Cart'} </button> ); }
'use client' to every component because it feels familiar. Resist this. Keep components as Server Components unless they specifically need useState, useEffect, browser APIs, or event handlers. The more Server Components you have, the less JavaScript your users download — and that's the whole point of the App Router.API Routes and Middleware: Building the Backend Inside Next.js
Next.js includes a built-in API layer. Files inside pages/api/ become serverless API endpoints — no separate backend server needed. This is the simplest way to add server-side logic to a Next.js application: form submissions, webhook handlers, authentication endpoints, and database queries all live inside the same project.
The App Router changes the API story. Instead of pages/api/, API routes live inside app/api/ as route.js files. Each HTTP method (GET, POST, PUT, DELETE) is exported as a named function. The handler receives a standard Web Request object and returns a standard Web Response — aligning with the web platform instead of Node.js-specific APIs.
Middleware runs before every request reaches your pages or API routes. It lives in middleware.js at the project root and is the right place for authentication checks, redirects, feature flags, and request logging. Middleware runs on the Edge Runtime by default — it executes at the CDN edge, not on your origin server, which means sub-millisecond latency for simple checks.
// --- App Router API Route --- // File: app/api/users/route.js // Each HTTP method is a named export import { NextResponse } from 'next/server'; // GET /api/users — list all users export async function GET(request) { const { searchParams } = new URL(request.url); const page = parseInt(searchParams.get('page') || '1'); const limit = parseInt(searchParams.get('limit') || '20'); // In production: call your database here const users = await db.users.findMany({ skip: (page - 1) * limit, take: limit, }); return NextResponse.json({ data: users, pagination: { page, limit }, }); } // POST /api/users — create a new user export async function POST(request) { const body = await request.json(); // Validate input if (!body.email || !body.name) { return NextResponse.json( { error: 'Email and name are required' }, { status: 400 } ); } const user = await db.users.create({ data: body }); return NextResponse.json({ data: user }, { status: 201 }); } // --- Middleware --- // File: middleware.js (project root) // Runs on every request before pages/API routes import { NextResponse } from 'next/server'; export function middleware(request) { // Example: redirect unauthenticated users const token = request.cookies.get('auth-token'); // Protect dashboard routes if (request.nextUrl.pathname.startsWith('/dashboard') && !token) { return NextResponse.redirect(new URL('/login', request.url)); } // Add request ID for tracing const response = NextResponse.next(); response.headers.set('x-request-id', crypto.randomUUID()); return response; } // Matcher controls which routes trigger middleware export const config = { matcher: ['/dashboard/:path*', '/api/protected/:path*'], };
- Runs on the Edge Runtime — executes at the CDN, not your origin server
- Sub-millisecond latency for simple checks like auth tokens and feature flags
- Cannot use Node.js APIs (fs, crypto.randomBytes) — only Web APIs available
- Use matcher config to limit which routes trigger middleware — avoid running on static assets
- The right place for: auth checks, redirects, A/B testing, request logging, rate limiting
Deployment and Performance: What Matters in Production
Understanding rendering strategies is only half the picture. In production, deployment configuration, caching headers, and build optimisation determine whether your app is fast or expensive. A perfectly coded Next.js app deployed without CDN caching is slower than a poorly coded app deployed with proper caching.
Vercel is the default deployment platform for Next.js — both are made by the same company. It handles ISR caching, edge functions, image optimisation, and preview deployments automatically. But Next.js deploys anywhere: AWS, Google Cloud, Docker containers, or static hosting for fully-static sites.
The three production levers: CDN caching (serve static assets from edge locations), ISR revalidation (serve cached pages, regenerate in background), and bundle analysis (ship less JavaScript to the browser). Pulling these three levers correctly is worth more than any code optimisation.
// File: next.config.js // Production configuration that actually matters /** @type {import('next').NextConfig} */ const nextConfig = { // Image optimisation domains — required for next/image with remote images images: { domains: ['images.example.com', 'cdn.example.com'], // Or use remotePatterns for more control (Next.js 13+) remotePatterns: [ { protocol: 'https', hostname: '**.example.com', }, ], }, // Headers: set caching policies for static assets async headers() { return [ { // Cache static assets (JS, CSS, images) for 1 year source: '/_next/static/:path*', headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], }, { // Cache API responses for 60 seconds source: '/api/:path*', headers: [ { key: 'Cache-Control', value: 'public, s-maxage=60, stale-while-revalidate=300', }, ], }, ]; }, // Redirects: handle legacy URLs and SEO async redirects() { return [ { source: '/old-blog/:slug', destination: '/blog/:slug', permanent: true, // 301 redirect — SEO-friendly }, ]; }, // Rewrites: proxy external APIs through Next.js async rewrites() { return [ { source: '/api/external/:path*', destination: 'https://external-api.example.com/:path*', }, ]; }, // Bundle analysis — run ANALYZE=true next build to visualise // Identifies large dependencies that should be code-split ...(process.env.ANALYZE === 'true' && { webpack: (config) => { const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); config.plugins.push( new BundleAnalyzerPlugin({ analyzerMode: 'static', reportFilename: './bundle-analysis.html', }) ); return config; }, }), }; module.exports = nextConfig; // --- Production Checklist --- // // 1. Run ANALYZE=true next build — check bundle-analysis.html for large deps // 2. Set Cache-Control headers for static assets (1 year, immutable) // 3. Set Cache-Control for API routes (s-maxage + stale-while-revalidate) // 4. Use next/image for all images — never use plain <img> // 5. Use next/link for all internal navigation — never use plain <a> // 6. Pre-render top pages with getStaticProps — serve from CDN // 7. Use ISR for pages that change infrequently — not full SSR // 8. Add middleware for auth — do not check auth in every page component
What Is Next.js? (The One Sentence That Actually Matters)
Next.js is React with a default build pipeline that bakes in routing, data fetching, and rendering strategies so you don't have to wire up Webpack yourself. React is a library for building UIs. Next.js is a framework that decides how those UIs get served to a browser.
The real distinction: React leaves you with a blank HTML file and a JavaScript bundle. Your users download the bundle, React renders the page. That works for dashboards. For public-facing sites where SEO and first paint matter, it's a disaster.
Next.js gives you SSR, SSG, ISR, and client-side rendering out of the box. You pick the strategy per page. It also handles code splitting, image optimization, and API routes. It's not magic — it's opinionated defaults that save you from yourself.
If you're shipping a production app that needs to load fast for real users on poor connections, you don't want to configure Babel and Webpack. You want Next.js. Period.
// io.thecodeforge — javascript tutorial // Without Next.js: manual SSR setup const express = require('express'); const React = require('react'); const { renderToString } = require('react-dom/server'); const app = express(); app.get('/', (req, res) => { const html = renderToString(React.createElement('h1', null, 'Hello')); res.send(`<html><body>${html}</body></html>`); }); // With Next.js: app/page.js handles this automatically export default function Page() { return <h1>Hello</h1>; }
Why To Use Next.js? (Because Default React Setup Is a Trap)
Default React apps suffer from three fatal flaws in production. First, the whole page is locked inside a single JavaScript bundle. On slow networks, that bundle takes forever to load. Users stare at a blank screen. Second, search engines don't execute JavaScript reliably. Your beautifully designed page gets indexed as empty divs. Third, routing isn't built in. You're forced to install react-router, configure it, and manage fallbacks yourself.
Next.js solves all three without you writing boilerplate. File-based routing means adding a file to the pages or app directory creates a route instantly. No router config. No fallback handling. The framework pre-renders pages at build time or request time, so search engines see real HTML. Code splitting happens per page automatically — users only download what they need.
You also get API routes inside the same project. Need a backend for form handling or auth? Add a file under pages/api. Done. No separate server. No CORS headaches.
The tradeoff is small: you surrender some control over build tooling. In return, you eliminate a mountain of setup that breaks constantly in production. If you've ever debugged a Webpack config at 2 AM, you already understand why that trade is worth it.
// io.thecodeforge — javascript tutorial // Bad: Manual React setup with CRA, then adding SSR later // npx create-react-app my-app // Choose this for internal tools // npm install react-router-dom // npm install express react-dom/server // Good: Next.js setup from the start // npx create-next-app@latest my-app // Choose this for public-facing apps // File structure creates routes // pages/index.js -> localhost:3000/ // pages/about.js -> localhost:3000/about
Key Features of Next.js (The Ones That Actually Move The Needle)
Next.js has a long feature list. Most of it is marketing fluff. These four features actually matter in production:
- File-based routing. Your folder structure becomes your URL structure. No router config files. No ambiguous path matching. Add a file, get a route. It's that simple. The App Router also supports layouts, loading states, and error boundaries at every route level.
- Hybrid rendering. You can mix SSR, SSG, ISR, and CSR in the same app. A marketing page can be statically generated at build time. A dashboard can use client-side rendering. A product page with frequent updates can use ISR to revalidate every 60 seconds. You decide per page, not per app.
- Automatic code splitting. Next.js splits your JavaScript by route. When a user visits /about, they download only the code for that page. The rest of the bundle stays on the server. This is the single biggest performance win over traditional SPAs.
- Built-in image optimization. The Image component automatically serves responsive images, lazy loads them, optimizes format, and prevents layout shift. It seems minor until your Lighthouse score jumps from 60 to 95 just by switching from <img> to <Image>.
Everything else — middleware, API routes, static exports — is convenient but secondary. These four features are why Next.js dominates production React.
// io.thecodeforge — javascript tutorial // app/page.js — Statically generated at build time export default function Home() { return <h1>Marketing page — SSG</h1>; } // app/dashboard/page.js — Client-side rendering export default function Dashboard() { // No async data fetching — loads in browser return <h1>Dashboard — CSR</h1>; } // app/products/[id]/page.js — ISR: revalidate every 60 seconds export default async function Product({ params }) { const data = await fetch(`https://api.example.com/products/${params.id}`, { next: { revalidate: 60 } }); return <h1>{data.name}</h1>; }
CSS Modules: Scoped Styles Without the Framework Headache
CSS Modules solve the global scope problem by generating unique class names at build time. No naming conventions, no BEM—just import a .module.css file and use it like an object. The why: traditional CSS cascades globally, so a class like 'button' in one component can break another. CSS Modules scope styles to the component by appending a hash to the class name. In Next.js, this works out of the box with both Pages and App Router—no configuration needed. The performance win: only the CSS used by a rendered component is included in the bundle. The trap: you cannot target child elements with regular CSS selectors inside a module. Instead, compose classes using the composes keyword or nest via the :global pseudo-selector. This approach is ideal for component libraries or any project where style isolation matters more than runtime CSS-in-JS overhead.
// io.thecodeforge — javascript tutorial import styles from './Button.module.css'; export default function Button({ variant, children }) { // Composes class based on prop, scoped automatically const className = `${styles.btn} ${styles[variant] || ''}`; return <button className={className}>{children}</button>; } // Button.module.css // .btn { padding: 8px 16px; border: none; } // .primary { background: blue; color: white; }
clsx with static module references.Next.js Basics 1: Installation and Project Scaffolding
Starting a Next.js project is a single command that sets up TypeScript, ESLint, Tailwind CSS, the App Router, and a src/ directory—all opt-in via prompts. The why: skipping the manual Webpack or Vite setup avoids configuration drift between environments. Run npx create-next-app@latest my-app and answer four questions: TypeScript (yes), ESLint (yes), Tailwind (no unless you need it), src/ directory (yes), App Router (yes), custom import alias (default). The result is a zero-config project with build optimization, image handling, and route segments ready. The performance insight: using src/ keeps your app logic separate from public/ assets and next.config.js. The common mistake: forgetting to run npm run dev instead of npm start during development—next start runs the production build. Test by visiting http://localhost:3000—you'll see the default Next.js page confirming the App Router is live.
// io.thecodeforge — javascript tutorial // Step 1: Create the project npx create-next-app@latest my-app --typescript --eslint --src-dir --app // Step 2: Enter directory and start dev server cd my-app npm run dev // Step 3: Build for production npm run build export NODE_ENV=production && npm start
npm run dev in production is a security risk—it exposes React error overlays and debugging info. Always use npm run build then npm start in deployment.How to Integrate Your Favorite Tools
Next.js acts as a blank canvas, but real projects demand specific tooling. The golden rule is to integrate at the right layer. For state management (Redux, Zustand), wrap your app in providers.js inside the app directory using a client component. For styling (Tailwind, Styled Components), install the library and configure tailwind.config.js or use a _document.js for global CSS-in-JS. For data fetching libraries (React Query, SWR), create a custom hook that calls your API routes — this keeps caching logic inside Next.js rather than duplicating it. For authentication (NextAuth, Clerk), use middleware to protect routes at the edge, not inside every page component. The key insight: never fight Next.js's file-based routing. If a tool needs global context, instantiate it in layout.js. If it's page-specific, keep it local. This prevents hydration mismatches and keeps your bundle lean.
// io.thecodeforge — javascript tutorial // Wrap Zustand store in a client component 'use client'; import { create } from 'zustand'; export const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); export default function Providers({ children }) { // Zustand doesn't need a provider — this file is for future tools return <>{children}</>; }
Practice Code Examples
Theory without code is just philosophy. Here's a practical challenge: build a real-time search filter that fetches from an API route and displays results with debouncing. The solution combines three Next.js patterns: a server-side API route for data, a client component for interactivity, and a custom hook for debouncing. First, create app/api/search/route.js to accept query parameters and return JSON. Then build a SearchBox client component with useState and useEffect. Import a debounce function (or write a 5-line one using setTimeout). Store the debounced value in state, then fetch from your API route using fetch('/api/search?q=' + debouncedQuery). Display results in a list. For bonus points, add loading and error states. This pattern mirrors real-world autocomplete, which is the most common Next.js interview question. The key principle: let the server do the heavy lifting, and keep the client lean.
// io.thecodeforge — javascript tutorial 'use client'; import { useState, useEffect } from 'react'; export default function SearchBox() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); useEffect(() => { const timer = setTimeout(async () => { const res = await fetch(`/api/search?q=${query}`); const data = await res.json(); setResults(data); }, 400); return () => clearTimeout(timer); }, [query]); return ( <div> <input onChange={(e) => setQuery(e.target.value)} placeholder="Search..." /> <ul>{results.map((r) => <li key={r.id}>{r.name}</li>)}</ul> </div> ); }
getServerSideProps on Every Page Crashed the Server Under Black Friday Traffic
- getServerSideProps runs a server function on every request — it is the most expensive rendering strategy
- ISR with revalidate: N serves cached pages and regenerates in the background — use it instead of SSR for data that changes infrequently
- Pre-build your most popular pages at deploy time — they serve instantly from CDN
- Use on-demand revalidation (res.revalidate) to invalidate specific pages when data changes — do not rely on time-based revalidation alone
next build 2>&1 | grep -E '●|λ|○'cat .next/server/pages/products/*.html 2>/dev/null | head -5grep -rn 'getStaticPaths' pages/ --include='*.js' --include='*.tsx'next build 2>&1 | grep -A5 'getStaticPaths'cat next.config.js | grep -A10 'images'curl -sI http://localhost:3000/_next/image?url=/test.jpg | head -10grep -rn 'typeof window\|Date.now\|Math.random' pages/ components/ --include='*.js' --include='*.tsx'grep -rn 'useEffect\|useState' pages/ --include='*.js' --include='*.tsx'| Feature | Pages Router (pages/) | App Router (app/) |
|---|---|---|
| Introduced in version | Next.js 1 (original) | Next.js 13 |
| Default component type | Client Component | Server Component |
| Data fetching method | getStaticProps / getServerSideProps | async/await directly in component |
| ISR support | revalidate in getStaticProps | { next: { revalidate: N } } in fetch |
| Client interactivity | All components can use hooks | Requires 'use client' directive |
| Layouts | Custom _app.js + _document.js | Nested layout.js files per folder |
| Learning curve | Lower — familiar React patterns | Higher — new mental model required |
| Production readiness | Battle-tested, widely used | Stable since Next.js 13.4 |
| Best for | Existing projects, teams new to Next.js | New projects, performance-critical apps |
| File | Command / Code | Purpose |
|---|---|---|
| ProductPage.js | export default function ProductPage() { | File-Based Routing |
| BlogPost.js | export default function BlogPost({ post }) { | SSG vs SSR vs CSR |
| ProductCard.js | export default function ProductCard({ product }) { | The Next.js Link and Image Components |
| ProductPageAppRouter.js | export default async function ProductPage({ params }) { | The App Router vs Pages Router |
| route.js | export async function GET(request) { | API Routes and Middleware |
| next.config.js | /** @type {import('next').NextConfig} */ | Deployment and Performance |
| WhyNext.js.js | const express = require('express'); | What Is Next.js? (The One Sentence That Actually Matters) |
| HybridRendering.js | export default function Home() { | Key Features of Next.js (The Ones That Actually Move The Nee |
| Button.js | export default function Button({ variant, children }) { | CSS Modules |
| Terminal.js | npx create-next-app@latest my-app --typescript --eslint --src-dir --app | Next.js Basics 1 |
| Providers.js | 'use client'; | How to Integrate Your Favorite Tools |
| SearchBox.js | 'use client'; | Practice Code Examples |
Key takeaways
Common mistakes to avoid
5 patternsUsing getServerSideProps everywhere for 'freshness'
Putting a plain <a> tag inside next/link
Ignoring the fallback option in getStaticPaths
Not setting Cache-Control headers for static assets
Using next/image without width and height props
Interview Questions on This Topic
What is the difference between getStaticProps and getServerSideProps, and how do you decide which one to use for a given page?
What are React Server Components in Next.js's App Router, and why can't you use useState or useEffect inside them?
If a page uses getStaticProps with revalidate: 60, and two users hit that page within the same minute — what does each user actually receive? Walk me through what Next.js does behind the scenes.
What is the difference between the Pages Router and the App Router in Next.js?
fetch() caching options. It supports nested layouts, streaming, and React Server Components.
The key differences: App Router ships less JavaScript (Server Components), has a simpler data fetching model (just use fetch), and supports nested layouts per route segment. Pages Router is simpler to learn and is used in most existing codebases. New projects should use the App Router. Existing projects can migrate gradually — both routers coexist in the same project.Frequently Asked Questions
Yes — Next.js is a framework built on top of React, not a replacement for it. You should be comfortable with React components, props, state, and hooks before starting with Next.js. The framework handles routing, rendering strategy, and optimisation on top of the React you already know.
No — this is one of the biggest misconceptions. Next.js supports static generation (SSG), server-side rendering (SSR), client-side rendering (CSR), and incremental static regeneration (ISR) all in the same project, on a page-by-page basis. Most production apps use a mix of all four strategies depending on what each page needs.
They represent two different routing and rendering systems that can coexist in one project. The pages/ folder uses the original Pages Router with getStaticProps and getServerSideProps. The app/ folder uses the newer App Router where components are Server Components by default and data fetching happens with async/await directly in components. New projects should use the App Router; existing projects can migrate gradually.
Yes. Next.js deploys anywhere: AWS (with SST or Amplify), Google Cloud, Docker containers, Netlify, or static hosting for fully-static sites. Vercel provides the tightest integration (automatic ISR caching, edge functions, preview deployments) but is not required. For self-hosted deployments, use next start with a Node.js server or export a static site with next export for fully-static applications.
Run next build — the output shows each page with a symbol: ● (static/SSG), λ (server/SSR), or ○ (static with ISR). In development mode, check the terminal output or use the Next.js DevTools browser extension. You can also check the response headers — static pages have different cache headers than server-rendered pages.
20+ years shipping production JavaScript and front-end systems at scale. Everything here is grounded in real deployments.
That's React.js. Mark it forged?
9 min read · try the examples if you haven't