Fixing Unexpected Caching Behavior Caused by Next.js's Full Route Cache During Soft Navigation via Link Components

May 24, 2025

#nextjs #bugfix

This blog covers how I fixed a caching bug in a Next.js App Router project. I ran into it while working on my personal blog, and the fix applies to Next.js 13 and up using the App Router.

Bug Description

Next.js has a default behavior where it caches the server-rendered result of a route on the server (Full Route Cache), and this cache can persist across sessions. That means a browser refresh on the client side doesn’t clear it.

Plus, navigating through the Next.js Link component performs a soft navigation, meaning Next.js intentionally tries to render cached content without fetching a new React Server Component payload. This can potentially lead to unexpected rendering behavior.

In my case, when the application is on /blog?tag=canvas and a browser refresh is performed, later navigations to /blog result in Next.js serving cached content from /blog?tag=canvas, which leads to incorrect page rendering, as shown in the following GIF.

bug reproduce

Fix with Client Side Router Refresh

The first approach for a fix is to leverage the client-side router.refresh() API. We invoke router.refresh() when the current page mounts, which clears the Client Side Router Cache for that route and makes a new request to the server for an updated React Server Component payload. This subsequently revalidates the page content.

"use client";

import { useRouter } from "next/navigation";
import { useEffect } from "react";

export function ClientComponent() {
    const router = useRouter();

	useEffect(() => {
	    // revalidates current path
	    router.refresh();
	}, [])

    return (<></>)
}

fix router refresh

This approach fixes the issue, as shown in the above GIF. However, it's pretty easy to notice that it impacts rendering performance as we're programmatically triggering a refresh, it causes an extra render cycle. This leads to excessive rendering, as demonstrated in the following GIF using React Scan.

overhead

Fix with Server Side Revalidation

A more server-oriented and performant fix is to use the revalidatePath API on the server side, which revalidates the content of a specified path on demand.

"use server";

import { revalidatePath } from "next/cache";

// server action to revalidate '/blog' path
export async function revalidateBlog() {
	revalidatePath("/blog");
}

To make this work, we need to create a server action that's triggered on navigation through links. This also means moving the Link component to the client side so we can attach a click event listener.

'use client';

import { revalidateBlog } from "app/blog/actions/actions";
import Link from "next/link";

export function ClientLinkComponent() {
	const handleRevalidateBlogRoute = () => {
        revalidateBlog()
	};

	return (
		<Link
			onClick={handleRevalidateBlogRoute}
            href={'/blog'}
		>
			All
		</Link>
	);
} 

We can now see from the React Scan results that rendering performance has improved.

fix revalidate

And here’s a clearer demonstration of the fix result without using React Scan.

final fix