Skip to content

Commit ddbdf29

Browse files
committed
fix(app): refactor 404 and 500 approach
1 parent e5e5cc8 commit ddbdf29

2 files changed

Lines changed: 63 additions & 53 deletions

File tree

.changeset/big-suns-wave.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+
Refactor `404` and `500` route handling for consistency and improved prerendering support

packages/astro/src/core/app/index.ts

Lines changed: 58 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ const clientLocalsSymbol = Symbol.for('astro.locals');
3434

3535
const responseSentSymbol = Symbol.for('astro.responseSent');
3636

37+
const STATUS_CODES = new Set([404, 500]);
38+
const STATUS_CODE_ROUTES = new Set(['/404', '/500']);
39+
3740
export interface MatchOptions {
3841
matchNotFound?: boolean | undefined;
3942
}
@@ -113,7 +116,7 @@ export class App {
113116
}
114117
return pathname;
115118
}
116-
match(request: Request, { matchNotFound = false }: MatchOptions = {}): RouteData | undefined {
119+
match(request: Request, _: MatchOptions = {}): RouteData | undefined {
117120
const url = new URL(request.url);
118121
// ignore requests matching public assets
119122
if (this.#manifest.assets.has(url.pathname)) {
@@ -125,38 +128,21 @@ export class App {
125128
if (routeData) {
126129
if (routeData.prerender) return undefined;
127130
return routeData;
128-
} else if (matchNotFound) {
129-
const notFoundRouteData = matchRoute('/404', this.#manifestData);
130-
if (notFoundRouteData?.prerender) return undefined;
131-
return notFoundRouteData;
132131
} else {
133132
return undefined;
134133
}
135134
}
136135
async render(request: Request, routeData?: RouteData, locals?: object): Promise<Response> {
137-
let defaultStatus = 200;
138136
if (!routeData) {
139137
routeData = this.match(request);
140138
if (!routeData) {
141-
defaultStatus = 404;
142-
routeData = this.match(request, { matchNotFound: true });
143-
}
144-
if (!routeData) {
145-
return new Response(null, {
146-
status: 404,
147-
statusText: 'Not found',
148-
});
139+
return this.#renderStatusCode(request, routeData, 404);
149140
}
150141
}
151142

152143
Reflect.set(request, clientLocalsSymbol, locals ?? {});
153-
154-
// Use the 404 status code for 404.astro components
155-
if (routeData.route === '/404') {
156-
defaultStatus = 404;
157-
}
158-
159-
let mod = await this.#getModuleForRoute(routeData);
144+
const defaultStatus = this.#getDefaultStatusCode(routeData.route);
145+
const mod = await this.#getModuleForRoute(routeData);
160146

161147
const pageModule = (await mod.page()) as any;
162148
const url = new URL(request.url);
@@ -179,47 +165,19 @@ export class App {
179165
);
180166
} catch (err: any) {
181167
error(this.#logging, 'ssr', err.stack || err.message || String(err));
182-
response = new Response(null, {
183-
status: 500,
184-
statusText: 'Internal server error',
185-
});
168+
return this.#renderStatusCode(request, routeData, 500);
186169
}
187170

188171
if (isResponse(response, routeData.type)) {
189-
// If there was a known error code, try sending the according page (e.g. 404.astro / 500.astro).
190-
if (response.status === 500 || response.status === 404) {
191-
const errorRouteData = matchRoute('/' + response.status, this.#manifestData);
192-
if (errorRouteData && errorRouteData.route !== routeData.route) {
193-
mod = await this.#getModuleForRoute(errorRouteData);
194-
try {
195-
const newRenderContext = await this.#createRenderContext(
196-
url,
197-
request,
198-
routeData,
199-
mod,
200-
response.status
201-
);
202-
const page = (await mod.page()) as any;
203-
const errorResponse = await tryRenderRoute(
204-
routeData.type,
205-
newRenderContext,
206-
this.#env,
207-
page
208-
);
209-
return errorResponse as Response;
210-
} catch {}
211-
}
172+
if (STATUS_CODES.has(response.status)) {
173+
return this.#renderStatusCode(request, routeData, response.status as 404 | 500);
212174
}
213175
Reflect.set(response, responseSentSymbol, true);
214176
return response;
215177
} else {
216178
if (response.type === 'response') {
217179
if (response.response.headers.get('X-Astro-Response') === 'Not-Found') {
218-
const fourOhFourRequest = new Request(new URL('/404', request.url));
219-
const fourOhFourRouteData = this.match(fourOhFourRequest);
220-
if (fourOhFourRouteData) {
221-
return this.render(fourOhFourRequest, fourOhFourRouteData);
222-
}
180+
return this.#renderStatusCode(request, routeData, 404);
223181
}
224182
return response.response;
225183
} else {
@@ -307,6 +265,53 @@ export class App {
307265
}
308266
}
309267

268+
#getDefaultStatusCode(route: string) {
269+
route = removeTrailingForwardSlash(route)
270+
if (route.endsWith('/404')) return 404;
271+
if (route.endsWith('/500')) return 500;
272+
return 200;
273+
}
274+
275+
/**
276+
* If is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
277+
* This also handles pre-rendered /404 or /500 routes
278+
*/
279+
async #renderStatusCode(request: Request, routeData: RouteData | undefined, status: 404 | 500) {
280+
const errorRouteData = matchRoute('/' + status, this.#manifestData);
281+
const url = new URL(request.url);
282+
if (errorRouteData) {
283+
if (errorRouteData.prerender) {
284+
const statusURL = new URL(`${this.#baseWithoutTrailingSlash}/${status}`, url);
285+
return fetch(statusURL.toString());
286+
}
287+
288+
const finalRouteData = routeData ?? errorRouteData;
289+
290+
let mod = await this.#getModuleForRoute(errorRouteData);
291+
try {
292+
const newRenderContext = await this.#createRenderContext(
293+
url,
294+
request,
295+
finalRouteData,
296+
mod,
297+
status
298+
);
299+
const page = (await mod.page()) as any;
300+
const errorResponse = await tryRenderRoute(
301+
finalRouteData.type,
302+
newRenderContext,
303+
this.#env,
304+
page
305+
);
306+
return errorResponse as Response;
307+
} catch {}
308+
}
309+
310+
const response = new Response(null, { status });
311+
Reflect.set(response, responseSentSymbol, true);
312+
return response;
313+
}
314+
310315
async #getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
311316
if (route.type === 'redirect') {
312317
return RedirectSinglePageBuiltModule;

0 commit comments

Comments
 (0)