diff --git a/.changeset/twenty-cherries-switch.md b/.changeset/twenty-cherries-switch.md new file mode 100644 index 0000000000..c79c53284e --- /dev/null +++ b/.changeset/twenty-cherries-switch.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a bug that caused the dev server to return an error if requesting "//" diff --git a/packages/astro/src/core/routing/3xx.ts b/packages/astro/src/core/routing/3xx.ts index fd4e211ed7..c05d7a894f 100644 --- a/packages/astro/src/core/routing/3xx.ts +++ b/packages/astro/src/core/routing/3xx.ts @@ -1,5 +1,5 @@ export type RedirectTemplate = { - from: string; + from?: string; location: string | URL; status: number; }; @@ -14,6 +14,6 @@ export function redirectTemplate({ status, location, from }: RedirectTemplate) { - Redirecting from ${from} to ${location} + Redirecting ${from ? `from ${from} ` : ''}to ${location} `; } diff --git a/packages/astro/src/vite-plugin-astro-server/base.ts b/packages/astro/src/vite-plugin-astro-server/base.ts index 84608ba508..4aa7e2a2d9 100644 --- a/packages/astro/src/vite-plugin-astro-server/base.ts +++ b/packages/astro/src/vite-plugin-astro-server/base.ts @@ -7,7 +7,9 @@ import { appendForwardSlash } from '@astrojs/internal-helpers/path'; import { bold } from 'kleur/colors'; import type { Logger } from '../core/logger/core.js'; import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js'; -import { writeHtmlResponse } from './response.js'; +import { writeHtmlResponse, writeRedirectResponse } from './response.js'; + +const manySlashes = /\/{2,}$/; export function baseMiddleware( settings: AstroSettings, @@ -21,7 +23,10 @@ export function baseMiddleware( return function devBaseMiddleware(req, res, next) { const url = req.url!; - + if (manySlashes.test(url)) { + const destination = url.replace(manySlashes, '/'); + return writeRedirectResponse(res, 301, destination); + } let pathname: string; try { pathname = decodeURI(new URL(url, 'http://localhost').pathname); diff --git a/packages/astro/src/vite-plugin-astro-server/response.ts b/packages/astro/src/vite-plugin-astro-server/response.ts index ef3c8247aa..753e77c66c 100644 --- a/packages/astro/src/vite-plugin-astro-server/response.ts +++ b/packages/astro/src/vite-plugin-astro-server/response.ts @@ -7,6 +7,7 @@ import { Readable } from 'node:stream'; import { getSetCookiesFromResponse } from '../core/cookies/index.js'; import { getViteErrorPayload } from '../core/errors/dev/index.js'; import notFoundTemplate from '../template/4xx.js'; +import { redirectTemplate } from '../core/routing/3xx.js'; export async function handle404Response( origin: string, @@ -53,6 +54,17 @@ export function writeHtmlResponse(res: http.ServerResponse, statusCode: number, res.end(); } +export function writeRedirectResponse(res: http.ServerResponse, statusCode: number, location: string) { + const html = redirectTemplate({ status: statusCode, location }); + res.writeHead(statusCode, { + Location: location, + 'Content-Type': 'text/html', + 'Content-Length': Buffer.byteLength(html, 'utf-8'), + }); + res.write(html); + res.end(); +} + export async function writeWebResponse(res: http.ServerResponse, webResponse: Response) { const { status, headers, body, statusText } = webResponse; diff --git a/packages/astro/test/dev-routing.test.js b/packages/astro/test/dev-routing.test.js index c6a19fc4e4..a43561769a 100644 --- a/packages/astro/test/dev-routing.test.js +++ b/packages/astro/test/dev-routing.test.js @@ -48,6 +48,19 @@ describe('Development Routing', () => { assert.equal(response.status, 200); }); + it('redirects when loading double slash', async () => { + const response = await fixture.fetch('//', { redirect: 'manual' }); + assert.equal(response.status, 301); + assert.equal(response.headers.get('Location'), '/'); + }); + + it('redirects when loading multiple slashes', async () => { + const response = await fixture.fetch('/////', { redirect: 'manual' }); + assert.equal(response.status, 301); + assert.equal(response.headers.get('Location'), '/'); + }); + + it('404 when loading invalid dynamic route', async () => { const response = await fixture.fetch('/2'); assert.equal(response.status, 404);