Skip to content

fix: strip base URL from path in SSR locale redirect#4000

Open
imlautaro wants to merge 1 commit into
nuxt-modules:mainfrom
imlautaro:fix/ssr-redirect-base-url-path
Open

fix: strip base URL from path in SSR locale redirect#4000
imlautaro wants to merge 1 commit into
nuxt-modules:mainfrom
imlautaro:fix/ssr-redirect-base-url-path

Conversation

@imlautaro

@imlautaro imlautaro commented Jun 1, 2026

Copy link
Copy Markdown

🔗 Linked issue

Resolves #3887 (duplicate: #3999)

❓ Type of change

  • 🐞 Bug fix (non-breaking change which fixes an issue)

📚 Description

When app.baseURL is set together with strategy: 'prefix', the SSR locale-redirect middleware logs:

[Vue Router warn]: The Matcher cannot resolve relative paths but received "ub/en/calendar". ...

Root cause — in src/runtime/server/plugin.ts (render:before hook), the route path was derived from url.pathname, which still contains app.baseURL, while the locale was detected from event.path, which Nitro has already stripped of the base:

const localeSegment = detector.route(event.path)            // "/en/calendar" -> "en"
const path = (pathLocale && url.pathname.slice(pathLocale.length + 1)) ?? url.pathname
// '/club/en/calendar'.slice(3) -> "ub/en/calendar"   (relative!)

That relative string reaches isExistingNuxtRoute(path) -> matcher.resolve({ path }), which vue-router rejects (it only accepts absolute paths). The match then fails and the redirect silently bails — so an unprefixed path under a base is never localized.

Fix — derive the path from event.path (already base-stripped) and run it through parsePath to drop any query string, keeping path an absolute, base-free route path. This also resolves the existing TODO in src/runtime/shared/matching.ts:

// TODO: path should not have base url - check if base url is stripped earlier

🧪 Test

Added specs/issues/3887.spec.ts: with app.baseURL: '/base-path' + strategy: 'prefix', an unprefixed path now redirects to its localized route.

  • With the fix: GET /base-path/about -> 302 (location contains /about) ✅
  • Without the fix: the same request returns 404 (redirect bailed) — so the test fails on the unpatched code, confirming it's a real regression test.

Note: I couldn't run the browser-based e2e specs locally (Playwright browser download was blocked in my environment), so I'm relying on CI for that matrix. The added spec is request-based and runs without a browser.

Checklist

  • Lint passes on the changed file
  • Added a regression test

Summary by CodeRabbit

  • Bug Fixes

    • Fixed server-side locale redirect middleware to correctly handle paths when app.baseURL is configured with i18n prefix strategy.
  • Tests

    • Added test coverage for locale redirect behavior with configured base URL.

The `render:before` redirect middleware derived the route path from
`url.pathname`, which still contains `app.baseURL`, while the locale was
detected from the already base-stripped `event.path`. With a base set this
produced a relative path (e.g. "se-path/about") that vue-router's matcher
rejected ("The Matcher cannot resolve relative paths"), so
`isExistingNuxtRoute` failed and the SSR locale redirect silently bailed.

Derive the path from `event.path` via `parsePath` so it stays an absolute,
base-free, query-free route path. Resolves the TODO in shared/matching.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR fixes a bug in the SSR locale-redirect middleware when app.baseURL is configured. The plugin now imports parsePath and updates the redirect handler to parse event.path directly instead of deriving the pathname from url.pathname, then strips the locale prefix from the parsed result. A new regression test validates that the middleware correctly redirects an unprefixed request to its localized route when both app.baseURL and i18n prefix strategy are active.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: strip base URL from path in SSR locale redirect' accurately summarizes the main change: modifying SSR locale redirect logic to handle app.baseURL correctly.
Linked Issues check ✅ Passed The code changes address all requirements from issue #3887: using event.path instead of url.pathname via parsePath, stripping locale prefix to produce absolute paths, and adding test coverage for the baseURL + prefix strategy scenario.
Out of Scope Changes check ✅ Passed All changes are in scope: the plugin modification fixes the core issue, the test verifies the fix, and both directly address the linked issue #3887 requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specs/issues/3887.spec.ts`:
- Around line 30-31: The redirect assertion is too broad—using
expect(res.headers.get('location')).toContain('/about') can match non-localized
paths; update the assertion to check the exact localized target (use the actual
locale your app uses) by replacing the toContain call on
res.headers.get('location') with a strict assertion against the full localized
path (e.g., expect(res.headers.get('location')).toBe('/en/about') or a regex
that asserts the locale prefix like
expect(res.headers.get('location')).toMatch(/^\/en\/about(\?|$)/)), keeping
references to res and headers.get('location') so the test verifies the localized
redirect explicitly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 212bb3d5-2502-489b-b2cf-63087fee9048

📥 Commits

Reviewing files that changed from the base of the PR and between bd54cb9 and f00de4b.

📒 Files selected for processing (2)
  • specs/issues/3887.spec.ts
  • src/runtime/server/plugin.ts

Comment thread specs/issues/3887.spec.ts
Comment on lines +30 to +31
expect(res.status).toBe(302)
expect(res.headers.get('location')).toContain('/about')

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen the redirect assertion to avoid false positives.

Line 31 can pass on non-localized redirects (e.g., /base-path/about). Assert the localized target explicitly.

Suggested test tightening
-    expect(res.headers.get('location')).toContain('/about')
+    expect(res.headers.get('location')).toBe('/en/about')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(res.status).toBe(302)
expect(res.headers.get('location')).toContain('/about')
expect(res.status).toBe(302)
expect(res.headers.get('location')).toBe('/en/about')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/issues/3887.spec.ts` around lines 30 - 31, The redirect assertion is
too broad—using expect(res.headers.get('location')).toContain('/about') can
match non-localized paths; update the assertion to check the exact localized
target (use the actual locale your app uses) by replacing the toContain call on
res.headers.get('location') with a strict assertion against the full localized
path (e.g., expect(res.headers.get('location')).toBe('/en/about') or a regex
that asserts the locale prefix like
expect(res.headers.get('location')).toMatch(/^\/en\/about(\?|$)/)), keeping
references to res and headers.get('location') so the test verifies the localized
redirect explicitly.

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.

[Vue Router warn]: The Matcher cannot resolve relative paths when baseURL and strategy: "prefix" is set

1 participant