Skip to content

fix(middleware): clear nextUrl.basePath for absolute paths outside basePath (part of #1830)#1872

Draft
james-elicx wants to merge 3 commits into
mainfrom
fix/issue-1830-middleware-base-path-absolute
Draft

fix(middleware): clear nextUrl.basePath for absolute paths outside basePath (part of #1830)#1872
james-elicx wants to merge 3 commits into
mainfrom
fix/issue-1830-middleware-base-path-absolute

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Part of #1830.

Failing test: test/e2e/middleware-base-path/test/index.test.ts — "Middleware base tests > should execute from absolute paths".

Mirrors Next.js getNextPathnameInfo: NextURL only treats basePath as active when the pathname actually starts with the configured prefix, so middleware receiving a request without the basePath prefix sees nextUrl.basePath === "" and can redirect it into the basePath. Strips basePath before matcher evaluation and from rewrite targets; passes the original (un-stripped) URL to middleware from both prod-server and deploy adapters. Includes tests/shims.test.ts coverage.

Recovered from a cut-off worker; CI + bonk to verify.

@pkg-pr-new

pkg-pr-new Bot commented Jun 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@1872
npm i https://pkg.pr.new/vinext@1872

commit: be242b4

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/bigbonk review — fix(middleware): clear nextUrl.basePath for absolute paths outside basePath

The core idea is correct and matches Next.js: getNextPathnameInfo only sets basePath when pathHasPrefix(pathname, basePath) (get-next-pathname-info.ts), and NextURL.analyze() does this.basePath = info.basePath ?? '' (next-url.ts). So clearing _basePath to "" when the pathname is outside basePath is the right semantics.

But the change has two blocking problems. The premise note ("Recovered from a cut-off worker; CI + bonk to verify") is accurate — CI is red and there's an App Router parity gap.


🔴 1. Breaks 10 existing unit tests in tests/shims.test.ts (CI red)

The PR updated the tests at lines 8178–8228 but left other tests that encode the old behavior untouched. Running vp test run tests/shims.test.ts -t "basePath" gives 10 failed / 39 passed:

  • L8315 href includes basePath prefix — expects http://localhost/app/dashboard, gets /dashboard
  • L8324 href includes both basePath and locale prefix
  • L8372 basePath setter to empty string clears basePath — expects /app, gets ""
  • L8440 searchParams mutations ... with basePath and locale
  • L8453 clone() preserves locale, basePath, and config
  • L8469 NextRequest passes basePath and i18n config through to nextUrlnew NextRequest("http://localhost/fr/dashboard", { nextConfig: { basePath: "/app", i18n } }) now yields nextUrl.basePath === "" instead of "/app"
  • L8487 / L8500 / L8542 NextRequest.url ...
  • L8615 applies trailingSlash with basePath

These tests all construct a NextURL/NextRequest with a basePath-stripped URL plus basePath: "/app" and expect basePath === "/app". They must be updated to use prefixed input URLs (e.g. http://localhost/app/fr/dashboard) to match the new (correct) semantics — exactly what the PR already did for the few tests it touched.

🔴 2. App Router regression — nextUrl.basePath is now wrong for in-basePath requests

This is the more serious issue. In Next.js, the middleware adapter builds the NextRequest from the original, un-stripped URL (adapter.ts: new NextURL(params.request.url, ...) where params.request.url still carries the basePath). The PR correctly compensates for this on the Pages side by re-adding the prefix in the runMiddleware closures (prod-server.ts:1676, deploy.ts:655).

But App Router was not given the same treatment. applyAppMiddleware passes normalizedPathname: cleanPathname (already basePath-stripped) into executeMiddleware (app-middleware.ts:243). createNextRequest then rebuilds mwUrl.pathname = cleanPathname (stripped) and constructs the NextRequest with nextConfig.basePath: "/app" (middleware-runtime.ts:201-218). With the new _stripBasePath, hasBasePath("/dashboard", "/app") is false, so:

  • Before: App Router middleware saw request.nextUrl.basePath === "/app" for /app/dashboard
  • After: it sees request.nextUrl.basePath === ""

This diverges from Next.js for the normal in-basePath case. It's exactly the failing L8469 test in miniature (stripped URL + basePath config). The fix needs to ensure App Router feeds the un-stripped pathname into createNextRequest (mirroring the Pages closures), or pass the basePath through so the NextRequest URL carries the prefix.

Note: there's no e2e test covering App Router middleware + basePath, so without a new test this regression would ship silently. Please add one (port from Next.js middleware-base-path).


🟡 Minor

  • prod-server.ts:1682 / deploy.ts:661 — the re-add logic mwUrl.pathname = basePath + (mwUrl.pathname === "/" ? "" : mwUrl.pathname) duplicates addBasePathToPathname from utils/base-path.ts. Prefer reusing that helper for consistency and to avoid a second copy of the root-path edge case.
  • middleware-runtime.ts:241-243matchPathname re-strips normalizedPathname. For App Router (cleanPathname already stripped) this is a no-op unless a real route legitimately starts with the basePath string after stripping; acceptable but worth a comment that it relies on cleanPathname already being canonical.

Verification

vp test run tests/shims.test.ts -t "basePath"
# Test Files  1 failed (1)
#       Tests  10 failed | 39 passed | 993 skipped

Verdict: correct direction, but not mergeable as-is — fix the 10 broken unit tests (use prefixed URLs) and close the App Router nextUrl.basePath gap with a corresponding test.

@ask-bonk

ask-bonk Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #1872.

Summary

The PR's core change to NextURL._stripBasePath() is correct in principle — I verified against Next.js source that getNextPathnameInfo only sets basePath when the pathname actually starts with the prefix, and NextURL.analyze() does this.basePath = info.basePath ?? ''. So clearing _basePath to "" for out-of-basePath paths is the right semantics.

But I found two blocking issues:

  1. 🔴 CI is red — 10 existing unit tests break. The PR updated only the tests at lines 8178–8228 but left 10 other tests/shims.test.ts tests that encode the old behavior (constructing NextURL/NextRequest with a stripped URL + basePath: "/app" and expecting basePath === "/app"). I confirmed with vp test run: 10 failed / 39 passed. These need prefixed input URLs.

  2. 🔴 App Router regression. Next.js's middleware adapter feeds the un-stripped URL into NextRequest. The PR correctly compensates on the Pages side (re-adding the prefix in the runMiddleware closures), but App Router still passes the stripped cleanPathname into createNextRequest. The result: App Router middleware now sees nextUrl.basePath === "" for normal in-basePath requests that previously reported "/app" — a silent parity regression (no e2e test covers it).

Plus two minor notes (duplicated addBasePathToPathname logic, and a clarity comment on the matchPathname re-strip).

Verdict: right direction, not mergeable as-is.

github run

…ession

Address two blocking issues from ask-bonk review on #1872:

1. Update 10 unit tests in tests/shims.test.ts that used basePath-stripped
   URLs (e.g. http://localhost/dashboard with basePath="/app") — these tests
   encoded the old incorrect behavior where basePath was always set from
   config regardless of the URL. Switch them to prefixed input URLs
   (http://localhost/app/dashboard) to match the new correct semantics.

2. Fix App Router regression: createNextRequest in middleware-runtime.ts was
   receiving normalizedPathname already stripped of basePath (App Router
   passes cleanPathname), so the NextRequest URL had no basePath prefix and
   _stripBasePath incorrectly cleared basePath to "". Fix by applying
   addBasePathToPathname before constructing the URL, mirroring the
   un-stripped URL that Next.js's adapter always passes to middleware.

Also use addBasePathToPathname helper in prod-server.ts and deploy.ts
closures instead of duplicating the root-path edge-case logic.
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk

ask-bonk Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

1 similar comment
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/bigbonk review — fix(middleware): clear nextUrl.basePath for absolute paths outside basePath

Re-reviewed at 98bc211. The two blockers from my previous review are now resolved, but the second commit introduced a new CI-red regression. Verdict: still not mergeable as-is, but much closer.

✅ Resolved since last review

  1. tests/shims.test.ts basePath tests are green. The PR updated the remaining tests to use basePath-prefixed input URLs. Verified:
    vp test run tests/shims.test.ts -t "basePath"
    #  Tests  49 passed | 993 skipped
    
  2. App Router nextUrl.basePath regression is fixed. createNextRequest now re-adds the basePath before constructing the NextRequest (middleware-runtime.ts:208-215):
    const mwPathname = basePath ? addBasePathToPathname(normalizedPathname, basePath) : normalizedPathname;
    So App Router (which passes the stripped cleanPathname) now feeds an un-stripped pathname into NextRequest, and NextURL strips it back → nextUrl.basePath === "/app" for in-basePath requests. This also resolves my earlier minor note about duplicated re-add logic — both Pages closures and App Router now route through addBasePathToPathname. The double-add in the Pages flow (closure re-adds, then createNextRequest re-adds) is safe because addBasePathToPathname is idempotent (base-path.ts:32 short-circuits when hasBasePath is true).

🔴 1. New regression — 9 tests fail in tests/deploy.test.ts (CI red)

The deploy.ts change wrapped runMiddleware in a closure and added addBasePathToPathname to the import, but the generated-entry assertions in tests/deploy.test.ts were not updated. Verified:

vp test run tests/deploy.test.ts
#  Tests  9 failed | 236 passed (245)

Failing tests:

  • handles middleware access control responses
  • handles middleware redirects
  • handles middleware rewrites
  • preserves responseHeaders on middleware redirect
  • proxies external middleware rewrites before local route handling
  • runs middleware before routing
  • preserves x-middleware-request-* headers for prod request override handling
  • builds reqCtx before middleware runs
  • handles basePath stripping and creates a new request with stripped URL for middleware

Two assertion shapes broke:

  • 8 sites at tests/deploy.test.ts:757,775,785,795,807,817,1126,1163 assert the literal 'runMiddleware: typeof runMiddleware === "function" ? runMiddleware : null', which no longer appears (it's now a closure).
  • 1 site at tests/deploy.test.ts:853 asserts 'import { hasBasePath, stripBasePath } from "vinext/utils/base-path"', but the import is now { addBasePathToPathname, hasBasePath, stripBasePath }.

These assertions need to be updated to match the new generated output (the closure body and the wider import).

🔴 2. No test covers the App Router nextUrl.basePath fix

The App Router regression fix (item ✅2 above) is the core behavioral change of this commit, but nothing tests it. tests/app-router-middleware-next-request.test.ts and tests/app-post-middleware-context.test.ts have zero basePath coverage, and there's no e2e middleware-base-path fixture. Without a test, this fix can silently regress again. Please add a unit test asserting that App Router middleware sees request.nextUrl.basePath === "/app" for an in-basePath request and === "" for an absolute (out-of-basePath) path — ideally ported from Next.js test/e2e/middleware-base-path.


🟢 Verified correct

  • NextURL._stripBasePath() clearing _basePath to "" when hasBasePath is false matches Next.js getNextPathnameInfo (basePath only set when the pathname starts with the prefix).
  • fetchEvent page change from normalizedPathnamematchPathname (middleware-runtime.ts:273) is correct: NextFetchEvent.sourcePage should be the basePath-stripped matched page.
  • The matcher re-strip (matchPathname, middleware-runtime.ts:251-253) correctly handles both flows: App Router's already-stripped cleanPathname (no-op) and the Pages flow's prefixed URL (strips before matching).
  • The rewrite-target strip (middleware-runtime.ts:399-409) is safe and idempotent for both flows.
  • vp check passes on all four changed source files.

Verdict: Direction is correct and both prior blockers are fixed. To merge: (1) update the 9 tests/deploy.test.ts assertions to match the new generated worker entry, and (2) add a test covering the App Router nextUrl.basePath behavior.

@ask-bonk

ask-bonk Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #1872.

Summary

Re-reviewed at commit 98bc211. The two blockers from the previous review are now fixed, but the second commit introduced a new CI-red regression.

✅ Resolved:

  1. The 10 broken tests/shims.test.ts basePath tests now pass (49 passing) — input URLs were prefixed.
  2. App Router nextUrl.basePath regression fixed — createNextRequest now re-adds basePath via addBasePathToPathname before constructing NextRequest, so in-basePath App Router requests correctly report basePath === "/app" again. The double-add in the Pages flow is safe (the helper is idempotent).

🔴 Two remaining blockers:

  1. 9 tests fail in tests/deploy.test.ts — the deploy.ts change wrapped runMiddleware in a closure and widened the base-path import, but the generated-entry assertions (8 sites asserting the old runMiddleware: typeof runMiddleware === "function" ? runMiddleware : null literal + 1 import assertion at line 853) were not updated. Verified 9 failed | 236 passed.
  2. No test covers the App Router fix — the core behavioral change of this commit has zero coverage; needs a unit test (ideally ported from Next.js middleware-base-path).

Everything else verified correct (_stripBasePath semantics vs Next.js, fetchEvent.pagematchPathname, matcher re-strip, rewrite-target strip, and vp check clean on all source files).

github run

…r change

Update 9 tests in deploy.test.ts that checked for the old simple runMiddleware
passthrough ('runMiddleware: typeof runMiddleware === "function" ? runMiddleware : null')
which no longer matches the generated code after the basePath re-add wrapper was
introduced in the deploy adapter.

Fix 1 test in app-route-handler-runtime.test.ts that incorrectly expected NextURL
to re-add the basePath prefix to a URL that was already stripped of it. Per
getNextPathnameInfo semantics (the fix this PR implements for #1830), basePath is
only set when the pathname actually starts with the configured prefix — a stripped
URL stays stripped.
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/bigbonk review — fix(middleware): clear nextUrl.basePath for absolute paths outside basePath

Re-reviewed at be242b4. The new commit (test(middleware): fix stale test assertions broken by basePath wrapper change) fixed 8 of the 9 deploy-test failures from the last review, and the App Router regression remains correctly fixed. But CI is still red — one assertion was missed.

🔴 1. One remaining CI-red failure in tests/deploy.test.ts

The previous commit updated the 8 runMiddleware literal assertions, but missed the import assertion at tests/deploy.test.ts:845. The deploy.ts import was widened to include addBasePathToPathname, so the generated worker entry now emits:

import { addBasePathToPathname, hasBasePath, stripBasePath } from "vinext/utils/base-path";

but the test still asserts the old two-symbol import:

expect(content).toContain(
  'import { hasBasePath, stripBasePath } from "vinext/utils/base-path"',
);

Verified locally:

vp test run tests/deploy.test.ts
#  Tests  1 failed | 244 passed (245)
#  ❯ tests/deploy.test.ts:844:21
#    "handles basePath stripping and creates a new request with stripped URL for middleware"

Fix: update line 845 to assert 'import { addBasePathToPathname, hasBasePath, stripBasePath } from "vinext/utils/base-path"' (or split into separate toContain("addBasePathToPathname") / toContain("stripBasePath") checks).

🔴 2. Still no test covers the App Router nextUrl.basePath fix

The core behavioral change of this PR — createNextRequest re-adding the basePath via addBasePathToPathname(normalizedPathname, basePath) (middleware-runtime.ts:208-210) so App Router middleware sees nextUrl.basePath === "/app" for in-basePath requests — still has zero coverage. tests/middleware-runtime.test.ts has no basePath assertions, and there's no executeMiddleware test exercising the stripped-normalizedPathname + basePath path.

tests/app-route-handler-runtime.test.ts was updated, but it only covers the NextURL out-of-basePath (stripped) case, not the App Router middleware in-basePath re-add. Please add a unit test asserting that executeMiddleware/createNextRequest with normalizedPathname: "/dashboard" + basePath: "/app" yields a NextRequest whose nextUrl.basePath === "/app" (ideally ported from Next.js test/e2e/middleware-base-path). Without it, the App Router fix can silently regress.


🟢 Verified correct

  • NextURL._stripBasePath() (shims/server.ts:340-347) clearing _basePath to "" when hasBasePath is false matches Next.js getNextPathnameInfo semantics. ✓
  • App Router fix is sound. app-rsc-handler.ts:413-419 documents that App Router only reaches middleware when the request is in-basePath (already stripped) or basePath is empty — hadBasePath is hardcoded true. So createNextRequest unconditionally re-adding the prefix is correct for every case App Router currently supports (basePath: false / absolute-path requests are an unrelated known follow-up to #1333). ✓
  • Pages flow parity. Both prod-server.ts:1670-1687 and deploy.ts:655-666 wrap runMiddleware in a closure that re-adds the prefix only when hadBasePath && basePath, correctly leaving out-of-basePath absolute requests stripped so middleware sees nextUrl.basePath === "". ✓
  • Idempotent double-add. addBasePathToPathname short-circuits when hasBasePath is already true (base-path.ts:32), so the Pages closure re-add + createNextRequest re-add is safe. ✓
  • Matcher re-strip (middleware-runtime.ts:251-253) correctly handles both flows. ✓
  • vp check clean on all 4 changed source files. ✓
  • tests/shims.test.ts -t "basePath"49 passed. ✓

Verdict: Direction is correct and the logic is sound, but not mergeable as-is — CI is red. To merge: (1) fix the missed import assertion at tests/deploy.test.ts:845, and (2) add a test for the App Router nextUrl.basePath re-add behavior.

@ask-bonk

ask-bonk Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #1872.

Summary

Re-reviewed at commit be242b4. The new commit fixed 8 of the 9 deploy-test failures, but CI is still red — one assertion was missed.

🔴 Two blockers

  1. One remaining CI-red failure in tests/deploy.test.ts:845. The previous commit updated the 8 runMiddleware literal assertions but missed the import assertion. The deploy.ts import widened to include addBasePathToPathname, so the generated entry now emits import { addBasePathToPathname, hasBasePath, stripBasePath }, but the test still asserts the old two-symbol import. Verified locally: 1 failed | 244 passed.

  2. No test covers the App Router nextUrl.basePath fix. The core behavioral change (createNextRequest re-adding basePath) has zero coverage — needs a unit test, ideally ported from Next.js middleware-base-path.

🟢 Verified correct

  • NextURL._stripBasePath() matches Next.js getNextPathnameInfo semantics.
  • The App Router fix is sound — app-rsc-handler.ts:413-419 documents App Router only reaches middleware in-basePath (hadBasePath hardcoded true), so the unconditional re-add is correct for all supported cases.
  • Pages flow parity (prod-server.ts, deploy.ts) correctly gates the re-add on hadBasePath && basePath.
  • addBasePathToPathname is idempotent, so the double-add in the Pages flow is safe.
  • vp check clean on all 4 changed source files; shims basePath tests pass (49).

Verdict: Logic is correct, but not mergeable as-is — fix the missed import assertion and add App Router coverage.

github run

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