Skip to content

Commit acac0af

Browse files
authored
fix(routing): middleware in dev (#12420)
1 parent e723e9e commit acac0af

File tree

6 files changed

+52
-9
lines changed

6 files changed

+52
-9
lines changed

.changeset/spicy-ties-matter.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 an issue where the dev server returns a 404 status code when a user middleware returns a valid `Response`.

packages/astro/src/core/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export const REWRITE_DIRECTIVE_HEADER_KEY = 'X-Astro-Rewrite';
3131

3232
export const REWRITE_DIRECTIVE_HEADER_VALUE = 'yes';
3333

34+
/**
35+
* This header is set by the no-op Astro middleware.
36+
*/
37+
export const NOOP_MIDDLEWARE_HEADER = 'X-Astro-Noop';
38+
3439
/**
3540
* The name for the header used to help i18n middleware, which only needs to act on "page" and "fallback" route types.
3641
*/
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
import type { MiddlewareHandler } from '../../@types/astro.js';
2+
import { NOOP_MIDDLEWARE_HEADER } from '../constants.js';
23

3-
export const NOOP_MIDDLEWARE_FN: MiddlewareHandler = (_, next) => next();
4+
export const NOOP_MIDDLEWARE_FN: MiddlewareHandler = (ctx, next) => {
5+
ctx.request.headers.set(NOOP_MIDDLEWARE_HEADER, 'true');
6+
return next();
7+
};

packages/astro/src/vite-plugin-astro-server/route.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type http from 'node:http';
22
import type { ComponentInstance, ManifestData, RouteData } from '../@types/astro.js';
33
import {
44
DEFAULT_404_COMPONENT,
5+
NOOP_MIDDLEWARE_HEADER,
56
REROUTE_DIRECTIVE_HEADER,
67
REWRITE_DIRECTIVE_HEADER_KEY,
78
clientLocalsSymbol,
@@ -191,16 +192,11 @@ export async function handleRoute({
191192

192193
mod = preloadedComponent;
193194

194-
const isDefaultPrerendered404 =
195-
matchedRoute.route.route === '/404' &&
196-
matchedRoute.route.prerender &&
197-
matchedRoute.route.component === DEFAULT_404_COMPONENT;
198-
199195
renderContext = await RenderContext.create({
200196
locals,
201197
pipeline,
202198
pathname,
203-
middleware: isDefaultPrerendered404 ? undefined : middleware,
199+
middleware: isDefaultPrerendered404(matchedRoute.route) ? undefined : middleware,
204200
request,
205201
routeData: route,
206202
});
@@ -213,10 +209,15 @@ export async function handleRoute({
213209
response = await renderContext.render(mod);
214210
isReroute = response.headers.has(REROUTE_DIRECTIVE_HEADER);
215211
isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
212+
const statusCodedMatched = getStatusByMatchedRoute(matchedRoute);
216213
statusCode = isRewrite
217214
? // Ignore `matchedRoute` status for rewrites
218215
response.status
219-
: (getStatusByMatchedRoute(matchedRoute) ?? response.status);
216+
: // Our internal noop middleware sets a particular header. If the header isn't present, it means that the user have
217+
// their own middleware, so we need to return what the user returns.
218+
!response.headers.has(NOOP_MIDDLEWARE_HEADER) && !isReroute
219+
? response.status
220+
: (statusCodedMatched ?? response.status);
220221
} catch (err: any) {
221222
const custom500 = getCustom500Route(manifestData);
222223
if (!custom500) {
@@ -308,3 +309,7 @@ function getStatusByMatchedRoute(matchedRoute?: MatchedRoute) {
308309
if (matchedRoute?.route.route === '/500') return 500;
309310
return undefined;
310311
}
312+
313+
function isDefaultPrerendered404(route: RouteData) {
314+
return route.route === '/404' && route.prerender && route.component === DEFAULT_404_COMPONENT;
315+
}

packages/astro/test/fixtures/middleware space/src/middleware.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,13 @@ const third = defineMiddleware(async (context, next) => {
7474
return next();
7575
});
7676

77-
export const onRequest = sequence(first, second, third);
77+
const fourth = defineMiddleware((context, next) => {
78+
if (context.request.url.includes('/no-route-but-200')) {
79+
return new Response("It's OK!", {
80+
status: 200
81+
});
82+
}
83+
return next()
84+
})
85+
86+
export const onRequest = sequence(first, second, third, fourth);

packages/astro/test/middleware.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ describe('Middleware in DEV mode', () => {
7070
assert.equal($('title').html(), 'MiddlewareNoDataOrNextCalled');
7171
});
7272

73+
it('should return 200 if the middleware returns a 200 Response', async () => {
74+
const response = await fixture.fetch('/no-route-but-200');
75+
assert.equal(response.status, 200);
76+
const html = await response.text();
77+
assert.match(html, /It's OK!/);
78+
});
79+
7380
it('should allow setting cookies', async () => {
7481
const res = await fixture.fetch('/');
7582
assert.equal(res.headers.get('set-cookie'), 'foo=bar');
@@ -239,6 +246,14 @@ describe('Middleware API in PROD mode, SSR', () => {
239246
assert.notEqual($('title').html(), 'MiddlewareNoDataReturned');
240247
});
241248

249+
it('should return 200 if the middleware returns a 200 Response', async () => {
250+
const request = new Request('http://example.com/no-route-but-200');
251+
const response = await app.render(request);
252+
assert.equal(response.status, 200);
253+
const html = await response.text();
254+
assert.match(html, /It's OK!/);
255+
});
256+
242257
it('should correctly work for API endpoints that return a Response object', async () => {
243258
const request = new Request('http://example.com/api/endpoint');
244259
const response = await app.render(request);

0 commit comments

Comments
 (0)