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.
8 minute lesson
A Server Component can await fetch(), an ORM, a database driver, or filesystem work directly. You do not need an effect 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 the 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 seems to use a name later. Authorization should select the records this user may see, and projection should select the fields the interface needs.
export default async function NotesPage() {
const response = await fetch('https://api.example.com/notes')
if (!response.ok) throw new Error('Could not load notes')
const notes = await response.json()
return <pre>{JSON.stringify(notes, null, 2)}</pre>
}
Load data from a small public endpoint or local data function. Handle non-2xx, empty, and malformed responses, and document the freshness decision. Log on the server and confirm neither the log nor an unused private field reaches the browser.
Lesson completed