Why Next.js Navigation Doesn’t Work as Expected

Published: (February 12, 2026 at 04:44 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Cover image for Why Next.js Navigation Doesn’t Work as Expected

We ran into an issue where clicking a menu item sometimes did not navigate to the expected page. The URL in the address bar would change correctly, but the content on the screen would not update. Clicking the menu repeatedly would occasionally make navigation work. The issue did not appear in local development, but only in the production environment.

Below is what we discovered and how we fixed it.

Reason

Next.js Sends an RSC Request When a “ Is Clicked

In the App Router, clicking a “ triggers a client‑side navigation instead of a full page reload. To render the next page, Next.js sends an internal request related to React Server Components (RSC) to fetch the server‑rendered data required for the transition. This allows the page to update without reloading the entire document.

What We Found in the Network Tab

While inspecting the Network tab during navigation, we saw these response headers:

x-middleware-rewrite: /rate-limit-error?retryAfter=1&_rsc=xxxx
x-nextjs-rewritten-path: /rate-limit-error
  • x-middleware-rewrite – the middleware rewrote the internal RSC request to the rate-limit-error route.
  • x-nextjs-rewritten-path – Next.js ultimately processed the request as /rate-limit-error.

Thus, the internal RSC request was being redirected to the rate‑limit error page.

Flow of the Issue

  1. The user clicks a “.
  2. Next.js sends an internal RSC request to fetch data for the next page.
  3. The rate‑limiting middleware incorrectly applies rate limiting to this internal request.
  4. The middleware rewrites the request to /rate-limit-error.
  5. The browser URL remains correct, but the fetched content comes from a different route.
  6. The page appears broken or does not update as expected.

Let’s fix

Before

The middleware applied rate limiting too broadly, affecting internal Next.js requests.

// Rate limit applied too broadly
const shouldRateLimit = true; // ← too broad

// Incomplete RSC detection
const hasRscParam = requestUrl.includes('_rsc=');
const hasRscHeader = request.headers.get('rsc') === '1';
const isRSCRequest = hasRscParam || hasRscHeader;

// Internal requests were not excluded
if (shouldRateLimit && isRateLimited) {
  return NextResponse.rewrite(
    new URL('/rate-limit-error', request.url)
  );
}

After

We limited rate limiting to API routes and non‑GET requests only, and explicitly excluded internal Next.js (RSC/router) requests.

// Rate limit only APIs and non-GET requests
const isApiRoute = pathname.startsWith('/api/');
const isNonGetRequest = request.method !== 'GET';
const shouldRateLimit = isApiRoute || isNonGetRequest;

// Accurate RSC detection
const hasRscParam = requestUrl.includes('_rsc=');
const hasRscHeader = request.headers.get('rsc') === '1';
const hasRscAccept =
  request.headers.get('accept')?.includes('text/x-component');
const isRSCRequest = hasRscParam || hasRscHeader || hasRscAccept;

// Exclude internal navigation requests
if (shouldRateLimit && !isRSCRequest && isRateLimited) {
  return NextResponse.rewrite(
    new URL('/rate-limit-error', request.url)
  );
}

To avoid similar issues in the future

  • Apply rate limiting only to API routes and non‑GET requests.
  • Explicitly exclude internal Next.js requests such as RSC and router‑related requests from middleware logic.
  • Be cautious when using rewrites in middleware, especially with client‑side navigation.
  • When navigation behaves inconsistently, inspect the Network tab and response headers for unexpected rewrites.

Following these practices helps ensure stable navigation and avoids hard‑to‑debug production issues.

0 views
Back to Blog

Related posts

Read more »