Visitar URL original
Respect path-segment boundaries when stripping the base in the dev se… · withastro/astro@8ae6b46 · GitHub
Skip to content

Commit 8ae6b46

Browse files
authored
Respect path-segment boundaries when stripping the base in the dev server (#17889)
1 parent 7a698ca commit 8ae6b46

3 files changed

Lines changed: 65 additions & 8 deletions

File tree

‎.changeset/spotty-bees-shout.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes a bug where the dev server stripped the configured `base` from URLs that only share a prefix with it. With `base: '/s'`, requests to `/src/...` were rewritten to `/rc/...` and failed, breaking those pages during development.

‎packages/astro/src/vite-plugin-astro-server/base.ts‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import { writeHtmlResponse } from './response.js';
1111
/**
1212
* Outcome of the base-URL evaluation for a dev-server request.
1313
*
14-
* - **`rewrite`** — The request URL starts with the configured `base` path.
15-
* Strip the base prefix so downstream handlers see a root-relative URL
16-
* (e.g. `/docs/about` → `/about` when `base: '/docs'`).
14+
* - **`rewrite`** — The request URL is the configured `base` path or lies
15+
* under it at a path-segment boundary. Strip the base prefix so downstream
16+
* handlers see a root-relative URL (e.g. `/docs/about` → `/about` when
17+
* `base: '/docs'`).
1718
* - **`not-found-subpath`** — The user navigated to `/` or `/index.html` but
1819
* the project has a non-root `base`. Respond with a 404 explaining that the
1920
* site lives under the base path, so the developer knows to update the URL.
@@ -52,10 +53,10 @@ export function resolveDevRoot(base: string, site?: string) {
5253
/**
5354
* Pure decision function for base-URL dev-server rewriting.
5455
*
55-
* Evaluates whether the incoming `url` starts with the project's `base` path
56-
* and returns the action the middleware should take. The async `fs.stat` branch
57-
* (checking `publicDir`) is represented as `check-public` and must be handled
58-
* by the caller.
56+
* Evaluates whether the incoming `url` is the project's `base` path or lies
57+
* under it at a path-segment boundary, and returns the action the middleware
58+
* should take. The async `fs.stat` branch (checking `publicDir`) is
59+
* represented as `check-public` and must be handled by the caller.
5960
*/
6061
export function evaluateBaseRewrite(
6162
url: string,
@@ -64,7 +65,9 @@ export function evaluateBaseRewrite(
6465
devRoot: string,
6566
devRootReplacement: string,
6667
): BaseRewriteDecision {
67-
if (pathname.startsWith(devRoot)) {
68+
// Match only at path-segment boundaries so a base like `/s` doesn't
69+
// swallow unrelated requests such as `/src/...` (same policy as #17701).
70+
if (pathname === devRoot || pathname.startsWith(appendForwardSlash(devRoot))) {
6871
let newUrl = url.replace(devRoot, devRootReplacement);
6972
if (!newUrl.startsWith('/')) newUrl = prependForwardSlash(newUrl);
7073
return { action: 'rewrite', newUrl };

‎packages/astro/test/units/dev/base-rewrite.test.ts‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,55 @@ describe('evaluateBaseRewrite — rewrite', () => {
9696
});
9797
// #endregion
9898

99+
// #region evaluateBaseRewrite — path-segment boundaries
100+
describe('evaluateBaseRewrite — path-segment boundaries', () => {
101+
it('does not rewrite a URL whose first segment merely starts with the base', () => {
102+
// base '/s' must not swallow '/src/...' (e.g. Vite virtual module requests
103+
// emitted without the base by the dev server).
104+
const result = evaluateBaseRewrite(
105+
'/src/pages/index.astro?astro&type=style&index=0&lang.css',
106+
'/src/pages/index.astro',
107+
undefined,
108+
'/s',
109+
'',
110+
);
111+
assert.equal(result.action, 'check-public');
112+
});
113+
114+
it('returns not-found for an HTML request whose first segment merely starts with the base', () => {
115+
const result = evaluateBaseRewrite(
116+
'/docs-archive/page',
117+
'/docs-archive/page',
118+
'text/html',
119+
'/docs',
120+
'',
121+
);
122+
assert.equal(result.action, 'not-found');
123+
});
124+
125+
it('still rewrites the exact base match', () => {
126+
const result = evaluateBaseRewrite('/s', '/s', undefined, '/s', '');
127+
assert.equal(result.action, 'rewrite');
128+
if (result.action === 'rewrite') {
129+
assert.equal(result.newUrl, '/');
130+
}
131+
});
132+
133+
it('still rewrites URLs under the base, preserving query params', () => {
134+
const result = evaluateBaseRewrite('/s/page?foo=bar', '/s/page', undefined, '/s', '');
135+
assert.equal(result.action, 'rewrite');
136+
if (result.action === 'rewrite') {
137+
assert.equal(result.newUrl, '/page?foo=bar');
138+
}
139+
});
140+
141+
it('does not rewrite a prefix-colliding segment when the base has a trailing slash', () => {
142+
const result = evaluateBaseRewrite('/docsX/page', '/docsX/page', undefined, '/docs/', '/');
143+
assert.equal(result.action, 'check-public');
144+
});
145+
});
146+
// #endregion
147+
99148
// #region evaluateBaseRewrite — not-found-subpath
100149
describe('evaluateBaseRewrite — not-found-subpath', () => {
101150
it('returns not-found-subpath for / when base is not /', () => {

0 commit comments

Comments
 (0)