Data and rendering

Fetch data on the server

Load data directly in an asynchronous Server Component and keep credentials and database access out of the browser bundle.

A Server Component can await fetch(), an ORM, a database driver, or filesystem work directly. You do not need a useEffect just to load the initial page data.

Check HTTP responses before parsing them and return a useful empty or error state. Server-side code may read non-public environment values, but rendered output and props passed to Client Components still reach the user. Never serialize a secret into the interface.

Call the underlying data function directly when it lives in the same application. Fetching your own Route Handler adds an HTTP hop, duplicates error translation, and makes the server depend on its own public URL. Keep one domain function that a page, action, and Route Handler can each call after applying their own boundary checks.

Current Next.js does not make every fetch persistently cached. Decide whether the result must be fresh, request-memoized, or reusable across requests, then express that choice with the APIs supported by your installed version. Authentication and per-user data are usually request-specific. A public catalog may tolerate deliberate caching.

Shape data before rendering it. A query returning whole user records can leak fields even if the JSX only shows a name later. Authorization should select the records this user may see. Projection should select the fields the interface needs.

export default async function NotesPage() {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=3')
  if (!response.ok) throw new Error('Could not load notes')
  const notes = await response.json()
  return <pre>{JSON.stringify(notes, null, 2)}</pre>
}

Load /notes and you should see a JSON array with three post objects. If the remote API is down, response.ok is false and the page throws. Handle that with an error boundary or a friendly message instead of a blank screen.

Log on the server and confirm neither the log nor an unused private field reaches the browser bundle. Document your freshness decision in a comment beside the fetch call.

Lesson completed