diff --git a/.changeset/wild-bees-sneeze.md b/.changeset/wild-bees-sneeze.md new file mode 100644 index 000000000000..2f66136cfe1f --- /dev/null +++ b/.changeset/wild-bees-sneeze.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixed an issue where a response with status code 404 led to an endless loop of implicit rerouting in dev mode. diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index da2c4b948a96..50d8d34d7e14 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -220,6 +220,7 @@ export async function handleRoute({ let response = await pipeline.renderRoute(renderContext, mod); if (response.status === 404 && has404Route(manifestData)) { const fourOhFourRoute = await matchRoute('/404', manifestData, pipeline); + if (fourOhFourRoute?.route !== options.route) return handleRoute({ ...options, matchedRoute: fourOhFourRoute, @@ -342,6 +343,6 @@ function getStatus(matchedRoute?: MatchedRoute): 404 | 500 | undefined { if (matchedRoute.route.route === '/500') return 500; } -function has404Route(manifest: ManifestData): RouteData | undefined { - return manifest.routes.find((route) => route.route === '/404'); +function has404Route(manifest: ManifestData): boolean { + return manifest.routes.some((route) => route.route === '/404'); } diff --git a/packages/astro/test/custom-404-implicit-rerouting.test.js b/packages/astro/test/custom-404-implicit-rerouting.test.js new file mode 100644 index 000000000000..4a5276c861d6 --- /dev/null +++ b/packages/astro/test/custom-404-implicit-rerouting.test.js @@ -0,0 +1,57 @@ +import { expect } from 'chai'; +import { loadFixture } from './test-utils.js'; + +for (const caseNumber of [ 1, 2, 3, 4 ]) { + describe(`Custom 404 with implicit rerouting - Case #${caseNumber}`, () => { + /** @type Awaited> */ + let fixture; + /** @type Awaited> */ + let devServer + + before(async () => { + fixture = await loadFixture({ + root: `./fixtures/custom-404-loop-case-${caseNumber}/`, + site: 'http://example.com' + }); + + devServer = await fixture.startDevServer(); + }); + + // sanity check + it('dev server handles normal requests', async () => { + const resPromise = fixture.fetch('/'); + const result = await withTimeout(resPromise, 1000); + expect(result).to.not.equal(timeout); + expect(result.status).to.equal(200); + }); + + it('dev server stays responsive', async () => { + const resPromise = fixture.fetch('/alvsibdlvjks'); + const result = await withTimeout(resPromise, 1000); + expect(result).to.not.equal(timeout); + expect(result.status).to.equal(404); + }); + + after(async () => { + await devServer.stop(); + }); + }); +} + + +/***** UTILITY FUNCTIONS *****/ + +const timeout = Symbol("timeout") + +/** @template Res */ +function withTimeout( + /** @type Promise */ + responsePromise, + /** @type number */ + timeLimit +) { + /** @type Promise */ + const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(timeout), timeLimit)) + + return Promise.race([ responsePromise, timeoutPromise ]); +} \ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-1/astro.config.mjs b/packages/astro/test/fixtures/custom-404-loop-case-1/astro.config.mjs new file mode 100644 index 000000000000..56004c9f9e06 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-1/astro.config.mjs @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-1/package.json b/packages/astro/test/fixtures/custom-404-loop-case-1/package.json new file mode 100644 index 000000000000..cbc4f22ed083 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-1/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/custom-404-loop-case-1", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/custom-404-loop-case-1/src/pages/404.astro b/packages/astro/test/fixtures/custom-404-loop-case-1/src/pages/404.astro new file mode 100644 index 000000000000..ff217453dd8e --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-1/src/pages/404.astro @@ -0,0 +1,4 @@ +--- +Astro.response.status = 404 +--- +

four oh four route

\ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-1/src/pages/index.astro b/packages/astro/test/fixtures/custom-404-loop-case-1/src/pages/index.astro new file mode 100644 index 000000000000..a65c81fdb2f1 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-1/src/pages/index.astro @@ -0,0 +1 @@ +

all good here... or is it?

\ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-2/astro.config.mjs b/packages/astro/test/fixtures/custom-404-loop-case-2/astro.config.mjs new file mode 100644 index 000000000000..56004c9f9e06 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-2/astro.config.mjs @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-2/package.json b/packages/astro/test/fixtures/custom-404-loop-case-2/package.json new file mode 100644 index 000000000000..2c5d3be559c8 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-2/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/custom-404-loop-case-2", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/custom-404-loop-case-2/src/pages/404.astro b/packages/astro/test/fixtures/custom-404-loop-case-2/src/pages/404.astro new file mode 100644 index 000000000000..0c079adca040 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-2/src/pages/404.astro @@ -0,0 +1,4 @@ +--- +return new Response(null, { status: 404 }) +--- +

four oh four route

\ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-2/src/pages/index.astro b/packages/astro/test/fixtures/custom-404-loop-case-2/src/pages/index.astro new file mode 100644 index 000000000000..a65c81fdb2f1 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-2/src/pages/index.astro @@ -0,0 +1 @@ +

all good here... or is it?

\ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-3/astro.config.mjs b/packages/astro/test/fixtures/custom-404-loop-case-3/astro.config.mjs new file mode 100644 index 000000000000..56004c9f9e06 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-3/astro.config.mjs @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-3/package.json b/packages/astro/test/fixtures/custom-404-loop-case-3/package.json new file mode 100644 index 000000000000..51b90b3cb5a4 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-3/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/custom-404-loop-case-3", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/custom-404-loop-case-3/src/middleware.js b/packages/astro/test/fixtures/custom-404-loop-case-3/src/middleware.js new file mode 100644 index 000000000000..992ca1de8d10 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-3/src/middleware.js @@ -0,0 +1,7 @@ +export async function onRequest(ctx, next) { + if (ctx.url.pathname !== '/') { + const response = await next() + return new Response(response.body, { ...response, status: 404 }) + } + return next(); +} \ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-3/src/pages/404.astro b/packages/astro/test/fixtures/custom-404-loop-case-3/src/pages/404.astro new file mode 100644 index 000000000000..af3c5ef0ae3f --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-3/src/pages/404.astro @@ -0,0 +1 @@ +

four oh four route

\ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-3/src/pages/index.astro b/packages/astro/test/fixtures/custom-404-loop-case-3/src/pages/index.astro new file mode 100644 index 000000000000..a65c81fdb2f1 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-3/src/pages/index.astro @@ -0,0 +1 @@ +

all good here... or is it?

\ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-4/astro.config.mjs b/packages/astro/test/fixtures/custom-404-loop-case-4/astro.config.mjs new file mode 100644 index 000000000000..56004c9f9e06 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-4/astro.config.mjs @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-4/package.json b/packages/astro/test/fixtures/custom-404-loop-case-4/package.json new file mode 100644 index 000000000000..0fb69f5139e0 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-4/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/custom-404-loop-case-4", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/custom-404-loop-case-4/src/middleware.js b/packages/astro/test/fixtures/custom-404-loop-case-4/src/middleware.js new file mode 100644 index 000000000000..6ab0cac69279 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-4/src/middleware.js @@ -0,0 +1,6 @@ +export function onRequest(ctx, next) { + if (ctx.url.pathname !== '/') { + return new Response(null, { status: 404 }); + } + return next(); +} \ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-4/src/pages/404.astro b/packages/astro/test/fixtures/custom-404-loop-case-4/src/pages/404.astro new file mode 100644 index 000000000000..af3c5ef0ae3f --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-4/src/pages/404.astro @@ -0,0 +1 @@ +

four oh four route

\ No newline at end of file diff --git a/packages/astro/test/fixtures/custom-404-loop-case-4/src/pages/index.astro b/packages/astro/test/fixtures/custom-404-loop-case-4/src/pages/index.astro new file mode 100644 index 000000000000..a65c81fdb2f1 --- /dev/null +++ b/packages/astro/test/fixtures/custom-404-loop-case-4/src/pages/index.astro @@ -0,0 +1 @@ +

all good here... or is it?

\ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index addc09144fc9..a70ac1969613 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2561,6 +2561,30 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/custom-404-loop-case-1: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + + packages/astro/test/fixtures/custom-404-loop-case-2: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + + packages/astro/test/fixtures/custom-404-loop-case-3: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + + packages/astro/test/fixtures/custom-404-loop-case-4: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/custom-404-md: dependencies: astro: