Skip to content

Make the base path readable from the browser bundle - #138

Merged
guanzhousongmicrosoft merged 1 commit into
documentdb:mainfrom
GuanzhouSong:fix-client-safe-base-path
Aug 4, 2026
Merged

Make the base path readable from the browser bundle#138
guanzhousongmicrosoft merged 1 commit into
documentdb:mainfrom
GuanzhouSong:fix-client-safe-base-path

Conversation

@GuanzhouSong

Copy link
Copy Markdown
Contributor

The defect

withBasePath reads NEXT_BASE_PATH, which is a private build variable — Next strips it from the browser bundle, where process.env.NEXT_BASE_PATH compiles to undefined.

Navbar is a client component and calls it at module scope, twice:

{label: "Blogs",href: withBasePath("/blogs/"),kind: "anchor"}// Navbar.tsx:37<Imagesrc={withBasePath("/images/DocumentDB Logo ....png")}/>// Navbar.tsx:106

Under a configured base path the static render emits /preview/blogs/; the same code after hydration emits /blogs/. The href changes under the reader, with a hydration mismatch alongside it.

Why it survived review twice

Nothing on disk shows it. The export is written by the server-side render, which reads the variable correctly, so the emitted markup is identical in the broken and fixed builds. A build-level assertion would pass either way — which is also why I have not added one. Only a real browser disagrees.

The same trap caught unit tests in #137: vitest runs in node, so tests exercised the one environment where the bug does not exist and went green while asserting the wrong thing.

The fix

next.config republishes the normalized value as NEXT_PUBLIC_BASE_PATH, which Next inlines into both bundles. sitePath prefers it, keeping the private variable as a fallback for server-only callers and standalone scripts that never see the republished one.

Deployments keep setting the single variable they already set. Every existing caller is fixed without being touched, including any added later — which matters more than the two current call sites, since the failure mode is silent.

Coverage

tests/sitePath.test.ts, with the environment stubbed both ways: the public variable, the private fallback, agreement between the two, slash normalization, an empty republished value meaning no base path, and relative and absolute URLs left alone.

Verified directly under node across all four combinations of the two variables. The suite itself is left to CI — npm ci fails against the registry proxy on my machine.

Relationship to #137

Found while fixing the same defect in the Markdown link resolver. That one is solved differently and deliberately: a resolved document link is an internal route, so it hands prefixing to next/link and reads no environment at all. This PR covers the cases that cannot do that — an anchor to /blogs/, which Next does not route, and an image src.

The two are independent and can merge in either order.

withBasePath reads NEXT_BASE_PATH, which is a private build variable, so
Next strips it from the browser bundle. Navbar is a client component and
calls it at module scope for the blogs anchor and the logo image: under a
configured base path the static render produced /preview/blogs/, and the
same code after hydration produced /blogs/, changing the href under the
reader and leaving React a mismatch to reconcile.
Nothing on disk shows this. The export is written by the server-side
render, which reads the variable correctly, so the emitted markup is
right in both the broken and the fixed build - only a real browser
disagrees. That is what made it survive review twice.
next.config now republishes the normalized value as
NEXT_PUBLIC_BASE_PATH, which Next inlines into both bundles, and
sitePath prefers it while keeping the private variable as a fallback for
server-only callers and standalone scripts that never see the
republished one. Deployments still set the single variable they already
set, and every existing caller is fixed without being touched, including
any added later.
Covered with the environment stubbed both ways: the public variable, the
private fallback, agreement between them, slash normalization, an empty
republished value meaning no base path, and relative and absolute URLs
left alone.
Exercised directly under node across all four combinations of the two
variables. The vitest suite is left to CI, since npm install fails
against the registry proxy on this machine.
Found while fixing the same defect in the Markdown link resolver
(documentdb#137), which solves it differently - that path is an internal route, so
it can hand the prefixing to next/link and read no environment at all.
@guanzhousongmicrosoft
guanzhousongmicrosoft merged commit ec78b7d into documentdb:mainAug 4, 2026
2 checks passed
Sign up for freeto 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.

2 participants

@GuanzhouSong@guanzhousongmicrosoft