Module F-4·23 min read

async/await directly in Server Components, fetch() with Next.js cache extensions, loading skeletons with Suspense, and the redirect/notFound control-flow functions.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

F-4 — Data Fetching in the App Router

Who this is for: Developers who understand the Server/Client Component boundary from F-3 and now want to know how to actually get data into their components. This module covers the practical mechanics — fetching data in Server Components, how Next.js extends fetch(), where Suspense fits in, and the control-flow helpers that keep your code clean.


The Old Way vs The New Way

In the Pages Router, data fetching was a page-level concern. getServerSideProps, getStaticProps, getStaticPaths — all of these were special functions that ran at the page boundary and drilled data down as props. Your components were passive recipients. A <ProductReviews> component couldn't fetch its own reviews — it had to receive them from the page that fetched everything.

In the App Router, every Server Component can fetch its own data. There's no special function, no getServerSideProps, no props drilling required. A <ProductReviews> component that needs reviews just queries for them directly:

tsx

No useEffect. No loading state. No useState. The component is async, it awaits its data, and it renders. Next.js handles the rest.


Fetching with fetch() — Next.js Extensions

Next.js extends the native fetch() API with additional options that control caching and revalidation behaviour. These only apply to fetch() calls inside Server Components and Route Handlers — they don't affect client-side fetches.

Static (Cached Forever)

tsx

In Next.js 14, fetch was cached by default. This changed in Next.js 15.

Dynamic (No Cache — Always Fresh)

tsx

Use no-store for data that must be current at request time: inventory counts, user-specific content, real-time prices.

Time-Based Revalidation

tsx

This is ISR at the fetch level. The first request uses the cached data. After 60 seconds, the next request triggers a background re-fetch. The user still gets a fast response from cache; the fresh data is stored for subsequent requests.

Tag-Based Revalidation

tsx
ts

Tag-based revalidation lets you invalidate specific cached data on demand — when a product is updated, for instance, you call revalidateTag('products') and the next request for any fetch tagged with 'products' will re-fetch. This is how you build CMS integrations where publishing content immediately updates the live site.


Next.js 15 Changed the Default

This is a migration trap worth understanding explicitly. In Next.js 14, fetch() was cached by default. In Next.js 15, fetch() is uncached by default — it behaves like cache: 'no-store' unless you explicitly opt in.

tsx

If you're upgrading a Next.js 14 project to 15, every fetch() call without an explicit cache option just became dynamic. Audit your data fetching on upgrade — your build output (the ○/λ symbols from F-1) will tell you if pages that should be static are now rendering dynamically.


Direct Database Access (No fetch() Required)

fetch() caching only applies to HTTP calls. When you're querying a database directly with Prisma, Drizzle, or raw SQL, you're not using fetch() at all — you're using a database client. In this case, Next.js's fetch() cache extensions don't apply.

tsx

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.