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
      Router Cache

      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.

      Router Cache

      1. How It Works

      Next.js maintains a client-side cache stored in memory during a user session, where it caches the React Server Component Payload (RSC Payload) by route segment. This is known as the router cache.
      Here’s a visual representation of how it works:
      
      The diagram is straightforward: when you first access route /a (MISS), Next.js caches the layout (/) and page (/a) segments in the router cache (SET). When you navigate to route /b, which shares the layout with /a, Next.js reuses the layout from the router cache and caches the page segment (/b). When you navigate back to /a, it directly uses the cached layout and page segments (HIT).
      In addition, when users navigate between routes, Next.js caches visited route segments and prefetches routes that users might navigate to (based on <Link> components within the viewport). This enhances the navigation experience by:
      • Enabling instant forward and backward navigation since previously visited routes are cached, and new routes are preloaded.
      • Avoiding page reloads during navigation while preserving React and browser state.
      Let’s verify this with a demo:
      // app/layout.js
      import Link from "next/link";
      
      export default function RootLayout({ children }) {
      return (
      <html lang="en">
      <body>
      <div>
      <Link href="/a">Link to /a</Link>
      <br />
      <Link href="/b">Link to /b</Link>
      </div>
      {children}
      </body>
      </html>
      )
      }
      Here’s the code for both routes:
      // app/a/page.js | app/b/page.js
      export default function Page() {
      return (
      <h1>Component X</h1>
      )
      }
      When you first visit /a, both /a and /b are preloaded because the <Link> components for both are within the viewport.
      
      Thanks to preloading and caching, navigating back and forth between these routes is smooth:
      
      

      2. Duration

      Router cache is stored in the browser’s temporary cache and lasts for the duration of the user session. There are two factors that determine its duration:
      • Session: The cache persists during navigation but is cleared when the page is refreshed.
      • Automatic Expiration: Each route segment automatically expires after a specific period:
        • Static Rendering: 5 minutes.
        • Dynamic Rendering: 30 seconds.
      For example, in the demo above, if you wait 5 minutes and then click the links again, the RSC Payload will be re-fetched.
      By adding prefetch={true} (default for <Link> components) or calling router.prefetch in dynamic routes, the cache can last for 5 minutes.

      3. Invalidation

      Router cache can be invalidated in a few ways:
      1. In Server Actions:
      2. Using revalidatePath or revalidateTag to revalidate data.
      3. Calling cookies.set or cookies.delete, which invalidates the router cache to prevent outdated routes (such as those related to authentication).
      4. Calling router.refresh, which invalidates the router cache and triggers a fresh request for the current route.
      4. Opting Out
      You cannot fully opt out of the router cache. However, you can pass false to the <Link> component’s prefetch prop to disable prefetching. The route segment will still be temporarily stored for 30 seconds to facilitate instant navigation between nested route segments. Also, visited routes are still cached.

      5. Real-World Experience

      While router cache may seem beneficial, let’s look at a scenario where it can be problematic.
      Consider the following directory structure:
      app
      ├─ (cache)
      │ ├─ about
      │ │ └─ page.js
      │ ├─ settings
      │ │ └─ page.js
      │ ├─ layout.js
      │ └─ loading.js
      
      Here’s the code for the layout:
      // app/(cache)/layout.js
      import Link from 'next/link'
      
      export const dynamic = 'force-dynamic'
      
      export default function CacheLayout({
      children,
      }) {
      return (
      <section className="p-5">
      <nav className="flex items-center justify-center gap-10 text-blue-600 mb-6">
      <Link href="/about">About</Link>
      <Link href="/settings">Settings</Link>
      </nav>
      {children}
      </section>
      )
      }
      And the code for the loading state:
      // app/(cache)/loading.js
      export default function DashboardLoading() {
      return <div className="h-60 flex-1 rounded-xl bg-indigo-500 text-white flex items-center justify-center">Loading</div>
      }
      Finally, the code for the /about and /settings pages:
      // app/(cache)/about/page.js
      const sleep = ms => new Promise(r => setTimeout(r, ms));
      
      export default async function About() {
      await sleep(2000)
      return (
      <div className="h-60 flex-1 rounded-xl bg-teal-400 text-white flex items-center justify-center">Hello, About! {new Date().toLocaleString()}</div>
      )
      }
      
      // app/(cache)/settings/page.js
      const sleep = ms => new Promise(r => setTimeout(r, ms));
      
      export default async function Settings() {
      await sleep(2000)
      return (
      <div className="h-60 flex-1 rounded-xl bg-teal-400 text-white flex items-center justify-center">Hello, Settings! {new Date().toLocaleString()}</div>
      )
      }
      Running this in production, you’ll notice the following behavior:
      
      
      At first glance, the interaction seems fine, but notice:
      • When you refresh the page, /about shows a loading state. When you first navigate to /settings, it also shows a loading state. However, subsequent clicks between /about and /settings do not trigger the loading state.
      • Also, there are no network requests after the first navigation.
      This is due to the client-side router cache. Since the RSC payload is cached, navigating between these routes doesn’t trigger new requests, and the displayed time remains unchanged.
      

      How to Ensure Fresh Data?

      1. Wait: Router cache has an automatic expiration—30 seconds for dynamic routes, 5 minutes for static routes. Waiting 30 seconds before clicking again will fetch fresh data.
      2. Use <a> Tags Instead of <Link>: Replace <Link> components with <a> tags to trigger a page reload, but this will result in a full page reload.
      // app/(cache)/layout.js
      import Link from 'next/link'
      
      export const dynamic = 'force-dynamic'
      
      export default function CacheLayout({
      children,
      }) {
      return (
      <section className="p-5">
      <nav className="flex items-center justify-center gap-10 text-blue-600 mb-6">
      <a href="/about">About</a>
      <a href="/settings">Settings</a>
      </nav>
      {children}
      </section>
      )
      }
      This solution triggers a full page reload, ensuring fresh data.
      
      3. Use router.refresh: Trigger a refresh of the current route using router.refresh. This approach requires converting your layout to a client component.
      // app/(cache)/layout.js
      'use client'
      
      import { useRouter } from 'next/navigation'
      
      export default function CacheLayout({
      children,
      }) {
      const router = useRouter()
      return (
      <section className="p-5">
      <nav className="flex items-center justify-center gap-10 text-blue-600 mb-6">
      <button onClick={() => {
      router.push('/about')
      router.refresh()
      }}>About</button>
      <button onClick={() => {
      router.push('/settings')
      router.refresh()
      }}>Settings</button>
      </nav>
      {children}
      </section>
      )
      }
      Adding export const dynamic = 'force-dynamic' to your pages app/(cache)/about/page.js and app/(cache)/about/page.js ensures they use dynamic rendering.
      export const dynamic = 'force-dynamic'
      Running the production version, the effect is as follows:
      
      4. Use NavigationEvents for Automatic Refresh:
      // app/(cache)/navigation-events.js
      'use client'
      
      import { useEffect } from 'react'
      import { usePathname, useSearchParams } from 'next/navigation'
      import { useRouter } from 'next/navigation'
      
      export function NavigationEvents() {
      const pathname = usePathname()
      const searchParams = useSearchParams()
      const router = useRouter()
      useEffect(() => {
      router.refresh()
      }, [pathname, searchParams])
      
      return null
      }
      Add this to your layout:
      // app/(cache)/layout.js
      import Link from 'next/link'
      import { Suspense } from 'react'
      import { NavigationEvents } from './navigation-events'
      
      export const dynamic = 'force-dynamic'
      
      export default function CacheLayout({
      children,
      }) {
      return (
      <section className="p-5">
      <nav className="flex items-center justify-center gap-10 text-blue-600 mb-6">
      <Link href={`/about`}>About</Link>
      <Link href={`/settings`}>Settings</Link>
      </nav>
      {children}
      <Suspense fallback={null}>
      <NavigationEvents />
      </Suspense>
      </section>
      )
      }
      Running the production version, the effect is as follows:
      
      

      Conclusion

      Router cache differs from full route cache in that:
      • Router cache happens during user sessions and temporarily stores RSC payloads in the browser. It lasts for the session and is cleared upon page refresh. Full route cache persists on the server and can be reused across multiple requests.
      • Full route cache only applies to statically rendered routes, while router cache applies to both static and dynamic routes.
      In practice, router cache can be both beneficial and challenging to manage. It’s frequently used, but since it cannot be fully disabled, you may need to implement special handling as shown above.

      Next.js Caching Summary Table

      API
      Router Cache
      Full Route Cache
      Data Cache
      Request Memoization
      <Link prefetch>
      Cache
      
      
      
      router.prefetch
      Cache
      
      
      
      router.refresh
      Revalidate
      
      
      
      fetch
      
      
      Cache
      Cache
      fetch options.cache
      
      
      Cache/Opt
      
      fetch options.next.revalidate
      
      Revalidate
      Revalidate
      
      fetch options.next.tags
      
      Cache
      Cache
      
      revalidateTag
      Revalidate
      Revalidate
      Revalidate
      
      revalidatePath
      Revalidate
      Revalidate
      Revalidate
      
      const revalidate
      
      Revalidate/Opt
      Revalidate/Opt
      
      const dynamic
      
      Cache/Opt
      Cache/Opt
      
      cookies
      Revalidate
      Opt out
      
      
      headers, searchParams
      
      Opt out
      
      
      generateStaticParams
      
      Cache
      
      
      React.cache
      
      
      Cache
      Cache
      In case of caching issues during development, refer to this table to identify the relevant caching type and choose an appropriate strategy for revalidation or opting out of caching.

      References

      • Building Your Application: Caching | Next.js