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.
- 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
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.
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.
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.
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.
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.
'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.
- 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.
- No Cache-Control headers — every request hits your server, even for unchanged static assets
- Using SSR (getServerSideProps) on pages that could use ISR — paying for server compute on every request
- Not using next/image — shipping unoptimised 4MB images destroys LCP and bandwidth costs
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.
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.
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.
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.
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.
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.
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.
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 -5Key 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?
Frequently Asked Questions
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?
12 min read · try the examples if you haven't