Skip to content

fix: register the router server context under the build-time project dir - #1229

Open
rome2o wants to merge 1 commit into
opennextjs:mainfrom
rome2o:fix/register-router-server-context
Open

fix: register the router server context under the build-time project dir#1229
rome2o wants to merge 1 commit into
opennextjs:mainfrom
rome2o:fix/register-router-server-context

Conversation

@rome2o

@rome2o rome2o commented Aug 28, 2026

Copy link
Copy Markdown

What

Register the Next.js router server context in the shared core, under the project directory key the route modules actually read.

Why

Next.js does not render pages router notFound results in the route module itself. It delegates to routerServerContext.render404, and falls back to a bare This page could not be found body when that context is missing (pages-handler.js).

NextNodeServer does register that context, but the write key and the read key are computed differently:

  • Write, in handleCatchallRenderRequest: path.relative(process.cwd(), this.dir)
  • Read, in the route module: the relativeProjectDir baked in at build time, which is always "" here

Those only agree when the working directory is the server directory. On AWS they do agree, because adapters/server-adapter.ts calls process.chdir(__dirname) on cold start. So the behaviour of a core rendering path depends on an adapter level chdir workaround.

Adapters that cannot change the working directory do not get that. On Cloudflare Workers there is no process.chdir, __dirname is bundled as "", and setNextjsServerWorkingDirectory is patched out (opennextjs/opennextjs-cloudflare#899). The context is written under ".." while the route modules read "", so render404 is never found and every notFound result serves the fallback body instead of the app's 404 page.

What changed

packages/open-next/src/core/util.ts registers the context immediately after constructing NextServer, keyed on the build-time relativeProjectDir. It no longer depends on the working directory, and it runs before server-adapter.ts calls chdir rather than after.

Also adds a ssr-not-found page to the pages router example and an e2e test, so the 404 path is covered rather than only the "route does not exist" path that 404.test.ts already covers.

No behaviour change on AWS

The entry lands under "", the same key Next.js already resolves to there, and Next.js still augments it per request. In particular isWrappedByNextServer is unaffected: Next.js writes that flag onto the same entry on every render request, so pre-registering cannot suppress it.

Verification

AWS, examples/pages-router built with openbuild:local:

  • /ssr-not-found/ returns 404 text/html, 2278 bytes, containing __NEXT_DATA__, rather than the fallback body
  • dynamic routes unchanged: /api/dynamic/hello-world-123 returns {"slug":"hello-world-123"}, /api/dynamic/catch-all/a/b returns {"slug":["a","b"]}
  • tests-unit green, biome and tsc --noEmit clean

Cloudflare, to confirm this fixes the adapter that is actually broken. I overlaid this build of core/util.js into opennextjs-cloudflare's vendored @opennextjs/aws, disabled the build patch from opennextjs/opennextjs-cloudflare#1346 entirely, and rebuilt the worker:

  • /ssr-not-found returns 404 HTML with __NEXT_DATA__ on the first request into a fresh isolate
  • full pages-router e2e suite: 37 passed, 1 skipped, including #1346's own test

So this change alone fixes Cloudflare and opennextjs/opennextjs-cloudflare#1346's ast-grep patch can be dropped once this lands. That PR is open against the Cloudflare adapter and I will close it in favour of this one.

Ref: opennextjs/opennextjs-cloudflare#1346, where @vicb suggested the fix belongs here.

Next.js delegates pages router `notFound` results to
`routerServerContext.render404`, falling back to a bare "This page could not be
found" body when that context is missing.

`NextNodeServer` registers the context itself, but keys it on
`path.relative(process.cwd(), server.dir)`, while the route modules read it back
using the `relativeProjectDir` baked in at build time, which is always an empty
string here. The two only line up because `server-adapter.ts` calls
`process.chdir(__dirname)` on cold start, so a core rendering path depends on
the working directory being changed by the adapter. Adapters that cannot change
the working directory register the context under a key nothing reads and serve
the fallback body instead of the app's 404 page.

Register the context upfront under the build-time key so it no longer depends on
the working directory. There is no behaviour change on AWS: the entry lands
under the same key Next.js already used there, and Next.js still augments it per
request.
@changeset-bot

changeset-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6c0e3b6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@opennextjs/aws Patch
app-pages-router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

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.

1 participant