Teachnique
      CourseRoadmaps
      Login

      Introduction

      Project SetupCLI

      IntroductionAPP RouterTemplateLoading UIError Handling404 Page

      Linking and Navigating

      Dynamic RoutesRoute groupsParallel RoutesIntercepting Routes

      Route handlersCacheCommon Questions

      Middleware

      CSRSSR & SSGISR

      React Server ComponentsServer-Side Rendering

      SuspenseStreaming

      Server & Client ComponentServer vs. Client ComponentBest Practices

      Server-Side Rendering Strategies

      Data Fetching & Caching & RedirectThird-Party Request Libraries on the Server SideBest Practices

      Request MemoizationData CacheFull Route CacheRouter Cache

      Server Actions IFormServer Actions IIOptimistic Update

      Tailwind CSSCSS-in-JSSass

      Environment VariablesAbsolute Imports& Module Path AliasesRoute Segment Config

      Image & LCPImage IImage IIOptimizing IOptimizing II

      Next FontFont ParameterApply Styles

      Feedback

      Submit request if you have any questions.

      Course
      Data Fetching & Caching & Redirect

      Next.js Mastery: From Fundamentals to Full-Stack

      Unlock the power of Next.js with this comprehensive course! Starting with the basics, you’ll learn essential skills such as routing, data fetching, and styling. Progress through practical projects, including building your own React Notes app, to gain hands-on experience. Dive into the source code to understand the inner workings and core principles of Next.js. Perfect for developers with a basic knowledge of React and Node.js, this course ensures you’ll be ready to create high-performance full-stack applications efficiently. Join us and master Next.js, backed by industry experts and a supportive learning community.

      Introduction

      How do you fetch data in Next.js?
      Next.js recommends using the native fetch method because it extends the native fetch with caching and revalidation mechanisms. This allows automatic reuse of request data to improve performance. However, if you are not familiar with it, you might encounter some "mysterious" situations...
      Let's see how to use it specifically.

      Fetching Data on the Server Side

      1. Basic Usage

      Next.js extends the native fetch Web API to configure caching and revalidating behavior for each server-side request. You can use fetch with async/await syntax in server components, route handlers, and Server Actions.
      For example:
      // app/page.js
      async function getData() {
      const res = await fetch('https://jsonplaceholder.typicode.com/todos')
      if (!res.ok) {
      // Handled by the nearest error.js
      throw new Error('Failed to fetch data')
      }
      return res.json()
      }
      
      export default async function Page() {
      const data = await getData()
      return <main>{JSON.stringify(data)}</main>
      }

      2. Default Caching

      By default, Next.js automatically caches the return value of server-side fetch requests (using Data Cache behind the scenes).
      // The cache option of fetch is used to control the caching behavior of the request
      // The default is 'force-cache', you can omit it while writing
      fetch('https://...', { cache: 'force-cache' })
      However, these situations won't automatically cache by default:
      • When used in Server Actions
      • When defined in route handlers with non-GET methods
      In simple terms, fetch returns results that are automatically cached when used in server components and route handlers with only GET methods.

      2.1. logging Configuration

      Let's illustrate this with examples. But before writing the code, let's modify the next.config.mjs configuration:
      const nextConfig = {
      logging: {
      fetches: {
      fullUrl: true
      }
      }
      };
      
      export default nextConfig;
      Currently, logging has only this one configuration, used to display fetch request and cache logs in development mode:
      GET /api/cache 200 in 385ms
      | GET https://dog.ceo/api/breeds/image/random 200 in 20ms (caches HIT)
      The log indicates:
      • The /api/cache route was accessed, which involved a GET request to the dog.ceo/api/breeds/… API.
      • The API returned in 20ms with status code 200.
      • The request hit the cache (HIT).
      These logs help us check the caching situation (though some log results are not very accurate and need improvement).

      2.2. Server Components

      First, let's use it in server components. Modify app/page.js as follows:
      async function getData() {
      // Each call to the API returns a random cat image data
      const res = await fetch('https://api.thecatapi.com/v1/images/search')
      if (!res.ok) {
      throw new Error('Failed to fetch data')
      }
      return res.json()
      }
      
      export default async function Page() {
      const data = await getData()
      return <img src={data[0].url} width="300" />
      }
      Run npm run dev to start development mode:
      
      In development mode, you can use a hard refresh (Command + Shift + R) in the browser to clear the cache, causing the data to change (cache: SKIP). With a regular refresh, since the cache is hit (cache: HIT), the data remains the same.
      Run npm run build && npm run start to start the production version:
      
      Because the fetch request's return result is cached, the image data remains the same regardless of whether it's a hard refresh or not.

      2.3. GET Request in Route Handlers

      Next, let's use it in route handlers. Create app/api/cache/route.js with the following code:
      export async function GET() {
      const res = await fetch('https://dog.ceo/api/breeds/image/random')
      const data = await res.json()
      return Response.json({ data, now: Date.now() })
      }
      Run npm run dev to start development mode:
      
      In development mode, a hard refresh in the browser skips the cache, while a regular refresh hits the cache. You can see that the first hard refresh request took 128ms, but subsequent regular refreshes return cached data in about 8ms.
      
      Run npm run build && npm run start to start the production version:
      
      Because the fetch request's return result is cached, the API data remains the same regardless of whether it's a hard refresh or not.

      3. Revalidation

      In Next.js, clearing the data cache and fetching the latest data is called revalidation. Next.js provides two ways to revalidate:
      1. Time-based revalidation: Revalidates data after a certain time when new requests are made, suitable for data that doesn't change frequently and doesn't require freshness.
      2. On-demand revalidation: Manually revalidates data based on events. This can be done using tag-based or path-based revalidation, suitable for scenarios that need to display the latest data quickly.

      Time-based Revalidation

      To use time-based revalidation, you need to set the next.revalidate option (in seconds) when using fetch:
      fetch('https://...', { next: { revalidate: 3600 } })
      Or configure it through route segment options, which revalidates all fetch requests in that route segment.
      // layout.jsx | page.jsx | route.js
      export const revalidate = 3600
      Note: In a statically rendered route, if you have multiple requests, each with different revalidation times, the shortest time will be used for all requests. For dynamically rendered routes, each fetch request will revalidate independently.

      On-demand Revalidation

      On-demand revalidation is done in route handlers or Server Actions using path-based (revalidatePath) or tag-based (revalidateTag) methods.
      revalidatePath
      Create app/api/revalidatePath/route.js with the following code:
      import { revalidatePath } from 'next/cache'
      
      export async function GET(request) {
      const path = request.nextUrl.searchParams.get('path')
      
      if (path) {
      revalidatePath(path)
      return Response.json({ revalidated: true, now: Date.now() })
      }
      
      return Response.json({
      revalidated: false,
      now: Date.now(),
      message: 'Missing path to revalidate',
      })
      }
      Accessing /api/revalidatePath?path=/ updates the fetch request data on the / route:
      
      Accessing /api/revalidatePath?path=/api/cache updates the fetch request data on the /api/cache route:
      
      Note: These GIFs demonstrate the behavior in development mode. The revalidatePath method updates the corresponding path's fetch cache results. However, in the production version, revalidatePath only affects pages, not route handlers.
      This is because /api/cache is statically rendered. To test revalidatePath in production, you need to switch /api/cache to dynamic rendering, which might involve using cookies or other functions that trigger Next.js's automatic logic to skip fetch caching.
      To test revalidatePath for route handlers in production, use this configuration:
      // Route dynamic rendering
      export const revalidate = 0
      // Force cache fetch
      export const fetchCache = 'force-cache'
      export async function GET() {
      const res = await fetch('https://dog.ceo/api/breeds/image/random')
      const data = await res.json()
      return Response.json({ data, now: Date.now() })
      }
      This code can be revalidated by revalidatePath in production, similar to the screenshots from development mode.
      
      revalidateTag
      Next.js has a route tagging system that allows revalidating multiple fetch requests across routes. The process involves:
      1. Tagging requests with one or more tags when using fetch.
      2. Calling revalidateTag to revalidate all requests with the specified tag.
      For example:
      javascriptCopy code// app/page.js
      export default async function Page() {
      const res = await fetch('https://...', { next: { tags: ['collection'] } });
      const data = await res.json();
      // ...
      }
      
      To revalidate all requests tagged with collection, call revalidateTag in a Server Action:
      javascriptCopy code// app/actions.js
      'use server';
      
      import { revalidateTag } from 'next/cache';
      
      export default async function action() {
      revalidateTag('collection');
      }
      
      Let's write a complete example. Modify app/page.js:
      Copy codeasync function getData() {
      const res = await fetch('https://api.thecatapi.com/v1/images/search', { next: { tags: ['collection'] } });
      if (!res.ok) {
      throw new Error('Failed to fetch data');
      }
      return res.json();
      }
      
      export default async function Page() {
      const data = await getData();
      return <img src={data[0].url} width="300" />;
      }
      Modify app/api/cache/route.js:
      export const revalidate = 0;
      export const fetchCache = 'force-cache';
      
      export async function GET() {
      const res = await fetch('https://dog.ceo/api/breeds/image/random', { next: { tags: ['collection'] } });
      const data = await res.json();
      return Response.json({ data, now: Date.now() });
      }
      Create app/api/revalidateTag/route.js:
      import { revalidateTag } from 'next/cache';
      
      export async function GET(request) {
      const tag = request.nextUrl.searchParams.get('tag');
      revalidateTag(tag);
      return Response.json({ revalidated: true, now: Date.now() });
      }
      Accessing /api/revalidateTag?tag=collection will revalidate both the / page and the /api/cache endpoint:
      

      Error Handling and Revalidation

      If an error occurs during revalidation, the cache continues to provide the last successfully generated data. Next.js will attempt to revalidate the data on the next request.

      4. Opting out of Data Caching

      fetch requests will opt out of data caching when:
      • The fetch request adds the cache: 'no-store' option
      • The fetch request adds the revalidate: 0 option
      • The fetch request is in a route handler and uses the POST method
      • The fetch request is used after using headers or cookies methods
      • The route segment option const dynamic = 'force-dynamic' is configured
      • The route segment option fetchCache is configured, which by default skips caching
      • The fetch request uses Authorization or Cookie request headers, and there's an uncached request above it in the component tree
      When using it specifically, if you don't want to cache a single request:
      // layout.js | page.js
      fetch('https://...', { cache: 'no-store' })
      To not cache multiple requests, you can use route segment configuration options:
      // layout.js | page.js
      export const dynamic = 'force-dynamic'
      Next.js recommends configuring the caching behavior of each request individually, which allows for more fine-grained control over caching behavior.