14 KiB
Next.js
But how does page rendering work with Next.js?
Terminology
- SSR: server-side rendering (Edge or Lambda)
- SSG: static site generation
- ISR: incremental static regeneration
DOM
Document Object Model: The in-browser tree of HTML elements representing the page.
Client
The user's environment, the browser. The front-end is the code running in that environment. On Compass, it can be any browser (Chrome, Firefox, etc.).
Server
Any remote infrastructure, i.e., not running in the user's environment / OS. The back-end is the code that runs in that environment. On Compass, there are two servers:
- Web server: hosted on Vercel at
compassmeet.com, which mostly provides the web pages to the client. That's the server we are talking about in the rest of the document. - Core server: hosted on Google Cloud at
api.compassmeet.com, a server with more resources and permissions to update the database. It's in charge of any operation related to non-web data (i.e., no HTML or CSS) such as accounts, profiles, messages, and votes.
React
React is a client-side UI library.
Its core job: create and update a virtual DOM, then reconcile that with the real DOM in the browser.
React itself does not define routing, data fetching conventions, or server rendering (it allows it, but doesn’t provide a full system).
Key behavior:
- React components run on the client by default.
- React can be rendered on the server (via frameworks like Next.js), but this requires additional tooling.
- React uses a Virtual DOM to compute minimal changes, then applies them to the real DOM.
Hydration
When a framework pre-renders HTML on the server, the browser receives static markup (HTML, JS and CSS). React then runs on the client and attaches event listeners and internal state to that markup.
Hydration bridges static HTML (build time) and interactive React behavior (run time).
You only need hydration if you have server-rendered HTML that must become interactive.
React re-renders a component whenever its state or props change. Hooks don’t cause re-renders by themselves — their returned values changing does.
React re-renders when:
-
A state updater runs
setState(...)fromuseStatedispatch(...)fromuseReducer
-
Parent props change
- Any parent re-render that produces new props for the child
-
Context value changes
- When a context provider updates its value, all consumers re-render
-
External stores change (in React 18+ “use sync external store” pattern)
useSyncExternalStore- Custom store hooks subscribing to something (auth store, Zustand, Redux, etc.)
-
Server → Client hydration mismatch forces a re-render
- Rare; usually an error condition
Re-rendering does NOT happen simply because:
- You called the component function manually
- A hook exists
- A ref changed (
useRef) - An effect ran (
useEffect) - A variable changed outside React state
Difference Between Re-rendering and Hydration
Hydration
- Happens once, right after the server-rendered HTML loads in the browser.
- React attaches event listeners and internal state structures to the existing DOM.
- Restores component tree in memory but does not replace DOM unless mismatched.
Hydration is startup initialization.
Re-rendering
- Happens after hydration, repeatedly, whenever React thinks the UI must update.
- Reconciliation compares new virtual DOM to old and mutates the real DOM minimally.
Important distinction:
- Variables inside components do not persist across renders.
- Only state, context, memoized values, refs, and hooks preserve information.
Next.js: What it adds
Next.js is a React framework that controls where code runs (server vs client), when it runs (build vs request time), and how HTML is generated. It adds routing, rendering strategies, data fetching conventions, and server infrastructure.
Next.js introduces:
- Server Components vs Client Components
- Route-based rendering
- Built-in server rendering pipelines
- Build-time optimizations
Client vs Server in Next.js
Server Components:
- Render on the server, never shipped to the client.
- Can safely access databases, filesystem, secrets.
- Output is serialized to HTML + a data format React uses to assemble the UI.
Client Components ("use client"):
- Render in the browser.
- Shipped to the client as JS bundles.
- Needed for interactivity (state, events, effects).
Notes:
- You can mix Server and Client components; the boundary matters for bundle size and where code executes.
- Only Client Components hydrate.
Build-Time vs Run-Time
Build-Time (during next build)
- The compiler analyzes the app, identifies server boundaries, optimizes routing.
- Static pages (SSG) are rendered to final HTML.
- Partial data may be pre-fetched if using static data functions.
- Bundles for client components are built.
Run-Time
- Server Components for SSR are executed on each request.
- Serverless or edge functions run as needed.
- Client Components hydrate and run effects in the browser.
Rendering Strategies
SSR (Server-Side Rendering)
- Used a webpage or API endpoint (to get server-side data like build ID).
- Generated on every request.
- Good for dynamic data that must be fresh.
- Initial load: server renders HTML → client hydrates.
- Runs server logic each time a user requests the page.
Can be dynamic or edge.
λ (Dynamic)
- Server-rendered on demand using Node.js
- Each request hits a Node.js server (or serverless function)
- Full access to Node APIs, filesystem, secrets, DB connections
- Typical use: SSR pages with dynamic data not suitable for edge
- Latency depends on server location
ℇ (Edge Runtime)
- Server-rendered on demand using Edge Runtime
- Runs on global edge nodes (CDN locations)
- Faster response due to geographic proximity
- Limited APIs: no filesystem, limited Node.js modules, mostly fetch and standard web APIs
- Typical use: low-latency SSR or ISR at the edge
SSG (Static Site Generation)
- HTML is generated at build time.
- Served as static files.
- Zero server cost at request time.
- Best for data that changes rarely.
ISR (Incremental Static Regeneration)
- A hybrid of SSG + scheduled revalidation.
- Page is generated at build, then regenerated in the background after a specified interval.
- Allows static pages with reasonably fresh content without full rebuilds.
Example behavior:
- First request after the revalidation window triggers a background regeneration.
- Users keep seeing the old page until the new one is ready, then updates swap in.
There are components that:
- Run once on the server to generate HTML (SSR phase)
- Then run again in the browser for hydration (client phase)
What Runs Where: Quick Table
| Task / Code | Build Time | Server Run Time | Client Run Time |
|---|---|---|---|
| Pre-rendering SSG pages | Yes | No | No |
| ISR regeneration | No | Yes (background) | No |
| SSR rendering | No | Yes | No |
| React event handling | No | No | Yes |
useEffect |
No | No | Yes |
| Server Component rendering | No | Yes | No |
| Client Component hydration | No | No | Yes |
1. Component Type Detection
A. Client Component (App Router)
A component is a Client Component if:
- The file begins with
"use client", or - It uses client-only hooks (
useState,useEffect,useRef, etc.), or - It references browser APIs (
window,document,localStorage, etc.), or - It uses client navigation (
Router.replace,useRouter), or - It uses interactive JSX handlers:
onClick,onSubmit, etc.
Implications:
- Runs only in browser
- Hydrates on the client
- No SSR of its data
- May receive HTML shell from server, but logic/data loads on client
Client Components = browser-only, hydrated, interactive.
B. Server Component (App Router)
A component is a Server Component if:
- No
"use client" - No client hooks
- No browser APIs
- No interactive handlers
- Uses server-only capabilities (DB queries, file system, server fetch, secrets)
Implications:
- Runs on server at build time (if static) and/or server at request time
- No hydration for the server part
- Can output HTML directly
- Can trigger SSG / SSR / ISR depending on cache mode or revalidate
3. Rendering Strategy (Pages Router Rules)
In the Pages Router, rendering is dictated entirely by which data-fetching function you export.
Below are the functions and exactly what they imply.
4. getServerSideProps — What It Does and What It Implies
export async function getServerSideProps(context) { ... }
What it does:
- Runs on the server for every request.
- Provides props to the page component.
- Has access to:
- database
- filesystem
- environment variables
- cookies, headers, auth context
What it implies:
- The page is SSR (Server-Side Rendered).
- HTML is generated on each request.
- No SSG or ISR possible.
- The page is never static.
Rendering outcome:
- SSR HTML + hydration for any client-side React in the page.
5. getStaticProps — What It Does and What It Implies
export async function getStaticProps(context) { ... }
What it does:
- Runs once at build time.
- Fetches data needed for static generation.
- Provides props to the component.
What it implies:
- The page is SSG (Static Site Generated).
- The output is static HTML + static JSON.
- Zero server rendering at request time.
Rendering outcome:
- Purely static HTML served from CDN.
- Hydration if component includes client logic (but no server execution).
When ISR occurs:
- If you return
{ revalidate: N }fromgetStaticProps, the page becomes ISR.
Example ISR config:
export async function getStaticProps() {
return {
props: { ... },
revalidate: 60 // seconds
}
}
6. getStaticPaths — What It Does and What It Implies
Used for dynamic SSG pages (e.g., [id].js).
export async function getStaticPaths() { ... }
What it does:
- Runs at build time.
- Tells Next.js which dynamic routes to pre-render.
- Works together with
getStaticProps.
What it implies:
- Page is SSG or ISR depending on
getStaticProps. - The routing structure is fixed at build time unless fallback mode is used.
Fallback modes define run-time:
fallback: false→ Only pages listed exist; 404 for othersfallback: true→ Generate pages at runtime, then cache them as staticfallback: "blocking"→ Block until server generates static page
Fallback generation effectively behaves like ISR for pages not pre-rendered.
7. Summary Table (Pages Router)
| Export | When It Runs | Rendering Model | Triggered By |
|---|---|---|---|
getServerSideProps |
On every request | SSR | Always dynamic |
getStaticProps |
Build time | SSG (or ISR with revalidate) |
No runtime server |
getStaticPaths |
Build time | SSG for dynamic routes | Works with getStaticProps |
8. Combining Rules: How to Infer Rendering from Code
If you see getServerSideProps:
- The page is always SSR
- Component receives props from server
- Component itself is a normal React component rendered server-side then hydrated
- No client data loading unless the component explicitly fetches in browser
If you see getStaticProps:
- The page is SSG or ISR
- Only runs again during revalidation
- Component is static unless you add client-side fetching
If you see getStaticPaths:
- The file uses dynamic SSG or ISR
- Builds static versions of dynamic routes
If you see "use client":
- Entire file is client-rendered
- Data in this component does not SSR
- Even if the page uses SSG/SSR, this component runs only in browser
If you see hooks (useState, useEffect, etc.):
- The component must be client-side
- It must hydrate
- It cannot participate in server rendering logic
- SSG/SSR/ISR still occurs for the page shell, but the logic inside this component runs only in browser
If you see server-side code (DB queries, secrets):
- Component must be Server Component (App Router) or handled inside
getServerSideProps/getStaticProps
How to Think About It When Architecting
-
Default to Server Components whenever no browser interactivity is needed.
Reduces bundle size and avoids unnecessary hydration. -
Use Client Components only where interaction happens (buttons, forms, animations, local state).
-
Choose a rendering model based on data volatility:
- Rarely changing: SSG
- Somewhat changing and OK with slightly stale: ISR
- Must always be fresh or personalized: SSR
-
Remember: Hydration cost scales with the amount of Client Components. Keep them narrow.
-
Consider caching:
Next.js can automatically cache server component results; knowing what is cached impacts performance heavily.
Backend vs Frontend on Next.js
| Term | True Meaning | Might Confuse People Because… |
|---|---|---|
| Frontend | Code that executes in the user’s environment (browser, WebView) | SSR code belongs to frontend logic but executes on server |
| Backend | Code that executes on remote infrastructure (server, VM, cloud function) | Some “backend-like” behavior can occur in browser via caching or local APIs |
Downtime
To simulate downtime you need the error to happen at runtime, not at build time. That means the page must be server-rendered, not statically generated.