Files
Compass/docs/Next.js.md
MartinBraquet edc7366b1d Improve docs
2025-11-15 13:05:26 +01:00

14 KiB
Raw Blame History

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 doesnt 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 dont cause re-renders by themselves — their returned values changing does.

React re-renders when:

  1. A state updater runs

    • setState(...) from useState
    • dispatch(...) from useReducer
  2. Parent props change

    • Any parent re-render that produces new props for the child
  3. Context value changes

    • When a context provider updates its value, all consumers re-render
  4. External stores change (in React 18+ “use sync external store” pattern)

    • useSyncExternalStore
    • Custom store hooks subscribing to something (auth store, Zustand, Redux, etc.)
  5. 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 } from getStaticProps, 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 others
  • fallback: true → Generate pages at runtime, then cache them as static
  • fallback: "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

  1. Default to Server Components whenever no browser interactivity is needed.
    Reduces bundle size and avoids unnecessary hydration.

  2. Use Client Components only where interaction happens (buttons, forms, animations, local state).

  3. 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
  4. Remember: Hydration cost scales with the amount of Client Components. Keep them narrow.

  5. 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 users 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.