Next.js / Basic: routes and execution boundaries / Lesson 5
Server data fetching, errors and source shape
Read authorized data through a server-only boundary and distinguish transport success from a valid domain record.
On this page
Lesson goals
- Choose explicit fetch policy under the primary non-Cache-Components mode.
- Validate source payloads and separate absence from source failure.
Keep source access close to the server use case
A Server Component can call an authorized data-access helper directly instead of fetching its own Route Handler over an internal HTTP loop. The extra hop can duplicate authentication assumptions and make build-time execution depend on an endpoint that is not running. Keep data access server-only and return a narrow model. A source response is untrusted until parsed and checked under its contract. Do not leak credentials through URLs, rendered errors or client props. A TypeScript assertion after response.json changes static belief, not the runtime shape or tenant eligibility of received records.
Make uncached request intent explicit
The fragment selects cache:no-store under the course’s primary mode and checks HTTP success before parsing JSON. It uses a synthetic .invalid endpoint and is never executed. no-store states the request-cache intent; it does not authorize data or guarantee the source itself is current. An explicit policy is easier to review than assuming every Server Component fetch is forever cached or always per-request. Keep default fetch behavior, route prerendering and development reuse distinct. A statically produced route can reuse output even when a simplistic summary says fetch is uncached by default.
async function loadPublicCard(id:string) {
const response=await fetch(`https://catalog.example.invalid/cards/${encodeURIComponent(id)}`,{cache:"no-store"});
if (!response.ok) throw new Error("CATALOG_UNAVAILABLE");
return validateCard(await response.json());
}
The synthetic endpoint is not contacted. validateCard and eligibility policy require real reviewed implementations.
Parallelize independent work without unbounded fanout
Awaiting unrelated source reads sequentially can create a waterfall, while starting one promise for every unbounded list item can overwhelm the source. Identify actual dependencies and bound cardinality before scheduling work. Promise.all does not cancel already-started calls when one rejects. Put deadlines and supported cancellation in the data adapter, and decide whether the view may show partial descriptive data or requires a complete authoritative result. A failed permission source must not become a permissive fallback just because a missing course summary can be tolerated. Source cost and UI streaming are related but different contracts.
Check a synthetic source record at runtime
The complete local validator accepts a small object with string id and title and returns only those fields. It rejects a number masquerading as title. This is a domain shape check, not a complete schema system or a security proof. Real sources may need length, version and eligibility rules and an explicit policy for unknown properties. The important distinction is that parsing JSON only recognizes syntax. Keep malformed source data separate from a legitimate absent record and avoid converting every error into an empty successful list that hides corruption or an outage.
function validateCard(value) {
if (!value || typeof value !== "object" || Array.isArray(value) || typeof value.id !== "string" || typeof value.title !== "string") throw new Error("INVALID_CARD");
return {id:value.id,title:value.title};
}
console.assert(validateCard({id:"c1",title:"Fetching",extra:"internal"}).title === "Fetching");
let rejected=false; try {validateCard({id:"c1",title:17});} catch {rejected=true;}
console.assert(rejected);
The valid fixture is projected and the wrong-type title is rejected. No fetch or Next render was performed.
Expose useful safe outcomes and preserve evidence
Return expected domain absence through the chosen not-found contract, and let unexpected source failures reach an appropriate error boundary with private details controlled. Record safe source categories and correlation IDs rather than full response bodies. Rendering errors can occur during build or request work, so identify the phase before changing cache flags. Test data helpers independently, then real Server Component and navigation behavior in an authorized isolated project. This course runs only the validator; source HTTP, cookies, request memoization, build prerendering and error-boundary behavior were not exercised. Avoid calling a successful local assertion a verified fetch integration.
Common errors and fixes
A JSON parse is called a validated record
- Why it happens
- Syntax recognition replaced runtime shape and domain checks.
- How to fix it
- Validate received values and project the public model.
- Prevent it next time
- Test wrong types and source failure separately.
A Server Component fetches its own unavailable API during build
- Why it happens
- An unnecessary internal HTTP loop coupled rendering to a live endpoint.
- How to fix it
- Call the authorized server data layer directly where appropriate.
- Prevent it next time
- Review build-time and request-time dependencies.
An unbounded parallel read overloads the source
- Why it happens
- Promise creation happened before cardinality control.
- How to fix it
- Bound inputs and source concurrency before starting work.
- Prevent it next time
- Measure safe call counts and cancellation in isolated integration.
Check your understanding
1. Does cache:no-store establish authorization or authoritative freshness?
Show worked solution
No. It controls the selected framework fetch-cache behavior. The source can still enforce the wrong tenant scope or return stale data from its own layer. Verify source eligibility and freshness independently, and distinguish fetch policy from route output reuse and browser state.
2. Why not treat every source failure as not-found?
Show worked solution
An outage or malformed record is not evidence that the requested object does not exist. Collapsing them can mislead users and hide operational faults, even when public disclosure intentionally keeps denied and absent objects similar. Preserve internal categories and a safe public error contract, with separate fixtures for absence and failure.
Key takeaways
- Validate source shape, eligibility and cache intent separately.
- A better loading experience does not automatically bound source work.