Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 16, 2025

Problem

During Next.js parallel page generation (next build), StaticRouter was using static class properties (LOCALE, PAGE_HREF, PARAMS) that are shared globally across all server components. This caused race conditions where multiple pages being generated simultaneously would overwrite each other's values, resulting in inconsistent Router.getPageHref() results and build failures with empty locale errors.

Example of the Issue

// Page component at /en/stuff/page.tsx
const Page = async () => {
  const pageHref = await Router.getPageHref()  // Should be '/en/stuff'
  return <Body pageHref={pageHref} />
}

// Body component
const Body = async ({ pageHref }: Props) => {
  const currentHref = await Router.getPageHref()  // Could be '/fr/truc' if parallel generation occurred!
  // pageHref !== currentHref during build ❌
}

During parallel build:

  1. Generation of /en/stuff begins: Router.setPageHref('/en/stuff')
  2. Generation of /fr/truc begins: Router.setPageHref('/fr/truc')overwrites global state
  3. Body component of /en/stuff renders: Router.getPageHref() returns '/fr/truc' or empty string ❌

This resulted in build errors like:

Error: Dictionary does not exists for locale "".

Solution

Replaced global static variables with React's cache() function to create request-scoped storage. React's cache() is specifically designed for React Server Components and ensures that each page render gets its own isolated context, preventing race conditions during parallel builds.

How It Works

React's cache() creates a per-request memoized function. During a single page render, all calls to getRequestContext() return the same object. Different page renders get different objects, eliminating race conditions:

// Create request-scoped storage using React's cache()
const getRequestContext = cache(() => ({
  locale: '',
  pageHref: '/',
  params: undefined,
}))

// Each call within a render returns the same context
Router.setLocale('en')  // Sets in current request's context
Router.getLocale()      // Gets from current request's context

Changes Made

Core Implementation

  • src/router/static-router.ts - Replaced global static variables with React's cache() for request-scoped storage; includes graceful fallback if React cache is unavailable

Key Features

Request Isolation - Each page render has its own isolated state
Backward Compatible - Falls back gracefully if React's cache is unavailable
Zero Template Changes - Existing setLocale/setPageHref calls work without modification
Well Tested - All 129 existing tests pass
Production Ready - Example app builds successfully without errors

Verification

# All tests pass
$ yarn test
Test Files  13 passed (13)
Tests  129 passed (129)

# Example app builds successfully
$ cd examples/basic && yarn build
✓ Compiled successfully
├ ○ /about                238 B     109 kB
├ ○ /cs/o-nas            238 B     109 kB  
├ ○ /es/sobre            238 B     109 kB

Fixes race conditions during parallel page generation by using React's cache() for request-scoped Router state isolation.

Fixes #401

Original prompt

This section details on the original issue you should resolve

<issue_title>Router.getPageHref returns inconsistent results</issue_title>
<issue_description>## Situation

I have a page component (default export in page.ts) that renders a body component. The 2 of them are server components.

const Page = async () => (
  const pageHref = await Router.getPageHref()
  return <Body pageHref={pageHref} />
)

In the body component: ops?!

I retrieve the pageHref from the body component props (passed down by the page component), and there, pageHref and await Router.getPageHref() give me 2 different values during the static generation (next build).

Hypothesis

I think that Router.setPageHref sets the pageHref value globally on the server. If 2 pages are generated in parallel, there are some conflicts:

  1. Generation of /en/stuff begins: Router.setPageHref('/en/stuff').
  2. Generation of /fr/truc begins: Router.setPageHref('/fr/truc).
  3. The body component of /en/stuff renders: await Router.getPageHref() return '/fr/truc'.
  4. Generation of /en/stuff ends.
  5. The body component of /fr/truc renders: await Router.getPageHref() return '/fr/truc'.
  6. Generation of /fr/truc ends.

Details

node 20.11.1
next 15.3.2
next-roots 4.0.6
</issue_description>

Comments on the Issue (you are @copilot in this section)

Fixes #401


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI self-assigned this Oct 16, 2025
@vercel
Copy link

vercel bot commented Oct 16, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
next-roots Error Error Oct 16, 2025 5:41am

Copilot AI changed the title [WIP] Fix inconsistent results from Router.getPageHref Fix Router.getPageHref race condition in parallel page generation Oct 16, 2025
Copilot AI requested a review from svobik7 October 16, 2025 05:42
@svobik7
Copy link
Owner

svobik7 commented Nov 19, 2025

@copilot fix the following build error:

Error occurred prerendering page "/about". Read more: https://nextjs.org/docs/messages/prerender-error
07:41:47
Error: Dictionary does not exists for locale "".
07:41:47
at o (/vercel/path0/examples/basic/.next/server/chunks/267.js:1:10689)
07:41:47
at async i (/vercel/path0/examples/basic/.next/server/app/(en)/about/page.js:1:3118)
07:41:47
Export encountered an error on /(en)/about/page: /about, exiting the build.
07:41:47
⨯ Static worker exited with code: 1 and signal: null
07:41:47
error Command failed with exit code 1.
07:41:47
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Copy link
Contributor Author

Copilot AI commented Nov 19, 2025

@copilot fix the following build error:

Error occurred prerendering page "/about". Read more: https://nextjs.org/docs/messages/prerender-error...

Fixed! The build error was caused by a race condition where global static variables were being overwritten during parallel page generation.

Solution: Replaced global variables with React's cache() function to create request-scoped storage. This ensures each page render gets its own isolated context.

Key change in commit 47dc1bb:

  • Modified StaticRouter to use getRequestContext() powered by React's cache()
  • Each page render now has isolated locale/href state
  • Falls back gracefully if React's cache is unavailable

Verified: Example app now builds successfully with all localized pages rendering correctly, including /about which was previously failing with "Dictionary does not exists for locale ''".

Build output shows all pages compiling successfully:

✓ Compiled successfully
├ ○ /about          238 B     109 kB
├ ○ /cs/o-nas       238 B     109 kB  
├ ○ /es/sobre       238 B     109 kB

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • download.cypress.io
    • Triggering command: /usr/local/bin/node index.js --exec install (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Router.getPageHref returns inconsistent results

2 participants