summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Emanuele Stoppa <my.burning@gmail.com> 2024-06-13 10:41:01 +0100
committerGravatar GitHub <noreply@github.com> 2024-06-13 10:41:01 +0100
commitd07d2f7ac9d87af907beaca700ba4116dc1d6f37 (patch)
tree3f2386318b4a64406ea129cf315b992f42cc60b8
parent8036b9a3fc5179737b465e4f6fea3144ecb57bc9 (diff)
downloadastro-d07d2f7ac9d87af907beaca700ba4116dc1d6f37.tar.gz
astro-d07d2f7ac9d87af907beaca700ba4116dc1d6f37.tar.zst
astro-d07d2f7ac9d87af907beaca700ba4116dc1d6f37.zip
fix: better DX for 500.astro in local development (#11244)
* wip * catch error in route.ts * catch error in route.ts * chore: tidy up error cases * log the original error * rebase * chore: reduce the scope of the 500 handling * we should not have a default 500 * remove props * remove unsure function, not needed * Update packages/astro/src/core/routing/astro-designed-error-pages.ts Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> * Update packages/astro/src/core/constants.ts Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> * changeset * relax the assertion * Update packages/astro/src/vite-plugin-astro-server/route.ts Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> * relax the assertion --------- Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
-rw-r--r--.changeset/olive-feet-eat.md9
-rw-r--r--packages/astro/src/core/constants.ts5
-rw-r--r--packages/astro/src/core/routing/astro-designed-error-pages.ts16
-rw-r--r--packages/astro/src/vite-plugin-astro-server/route.ts23
-rw-r--r--packages/astro/test/core-image.test.js4
-rw-r--r--packages/astro/test/fixtures/rewrite-runtime-error-custom500/astro.config.mjs9
-rw-r--r--packages/astro/test/fixtures/rewrite-runtime-error-custom500/package.json8
-rw-r--r--packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/500.astro4
-rw-r--r--packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/errors/start.astro3
-rw-r--r--packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/errors/throw.astro3
-rw-r--r--packages/astro/test/rewrite.test.js81
-rw-r--r--pnpm-lock.yaml6
12 files changed, 163 insertions, 8 deletions
diff --git a/.changeset/olive-feet-eat.md b/.changeset/olive-feet-eat.md
new file mode 100644
index 000000000..6984b2956
--- /dev/null
+++ b/.changeset/olive-feet-eat.md
@@ -0,0 +1,9 @@
+---
+'astro': patch
+---
+
+Improves the developer experience of the custom `500.astro` page in development mode.
+
+Before, in development, an error thrown during the rendering phase would display the default error overlay, even when users had the `500.astro` page.
+
+Now, the development server will display the `500.astro` and the original error is logged in the console.
diff --git a/packages/astro/src/core/constants.ts b/packages/astro/src/core/constants.ts
index a4d32abe5..0930ea6f0 100644
--- a/packages/astro/src/core/constants.ts
+++ b/packages/astro/src/core/constants.ts
@@ -33,6 +33,11 @@ export const ROUTE_TYPE_HEADER = 'X-Astro-Route-Type';
export const DEFAULT_404_COMPONENT = 'astro-default-404.astro';
/**
+ * The value of the `component` field of the default 500 page, which is used when there is no user-provided 404.astro page.
+ */
+export const DEFAULT_500_COMPONENT = 'astro-default-500.astro';
+
+/**
* A response with one of these status codes will be rewritten
* with the result of rendering the respective error page.
*/
diff --git a/packages/astro/src/core/routing/astro-designed-error-pages.ts b/packages/astro/src/core/routing/astro-designed-error-pages.ts
index 6a047f6d5..f6785cbdc 100644
--- a/packages/astro/src/core/routing/astro-designed-error-pages.ts
+++ b/packages/astro/src/core/routing/astro-designed-error-pages.ts
@@ -1,6 +1,6 @@
import type { ManifestData, RouteData } from '../../@types/astro.js';
import notFoundTemplate from '../../template/4xx.js';
-import { DEFAULT_404_COMPONENT } from '../constants.js';
+import { DEFAULT_404_COMPONENT, DEFAULT_500_COMPONENT } from '../constants.js';
export const DEFAULT_404_ROUTE: RouteData = {
component: DEFAULT_404_COMPONENT,
@@ -16,6 +16,20 @@ export const DEFAULT_404_ROUTE: RouteData = {
isIndex: false,
};
+export const DEFAULT_500_ROUTE: RouteData = {
+ component: DEFAULT_500_COMPONENT,
+ generate: () => '',
+ params: [],
+ pattern: /\/500/,
+ prerender: false,
+ pathname: '/500',
+ segments: [[{ content: '500', dynamic: false, spread: false }]],
+ type: 'page',
+ route: '/500',
+ fallbackRoutes: [],
+ isIndex: false,
+};
+
export function ensure404Route(manifest: ManifestData) {
if (!manifest.routes.some((route) => route.route === '/404')) {
manifest.routes.push(DEFAULT_404_ROUTE);
diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts
index b451a8ff6..b05412314 100644
--- a/packages/astro/src/vite-plugin-astro-server/route.ts
+++ b/packages/astro/src/vite-plugin-astro-server/route.ts
@@ -41,6 +41,11 @@ function getCustom404Route(manifestData: ManifestData): RouteData | undefined {
return manifestData.routes.find((r) => route404.test(r.route));
}
+function getCustom500Route(manifestData: ManifestData): RouteData | undefined {
+ const route500 = /^\/500\/?$/;
+ return manifestData.routes.find((r) => route500.test(r.route));
+}
+
export async function matchRoute(
pathname: string,
manifestData: ManifestData,
@@ -273,7 +278,22 @@ export async function handleRoute({
});
}
- let response = await renderContext.render(mod);
+ let response;
+ try {
+ response = await renderContext.render(mod);
+ } catch (err: any) {
+ const custom500 = getCustom500Route(manifestData);
+ if (!custom500) {
+ throw err;
+ }
+ // Log useful information that the custom 500 page may not display unlike the default error overlay
+ logger.error('router', err.stack || err.message);
+ const filePath = new URL(`./${custom500.component}`, config.root);
+ const preloadedComponent = await pipeline.preload(custom500, filePath);
+ response = await renderContext.render(preloadedComponent);
+ status = 500;
+ }
+
if (isLoggedRequest(pathname)) {
const timeEnd = performance.now();
logger.info(
@@ -321,6 +341,7 @@ export async function handleRoute({
await writeSSRResult(request, response, incomingResponse);
return;
}
+
// Apply the `status` override to the response object before responding.
// Response.status is read-only, so a clone is required to override.
if (status && response.status !== status && (status === 404 || status === 500)) {
diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js
index 17c08db21..a4fd13fcb 100644
--- a/packages/astro/test/core-image.test.js
+++ b/packages/astro/test/core-image.test.js
@@ -712,7 +712,7 @@ describe('astro:image', () => {
let res = await fixture.fetch('/get-image-empty');
await res.text();
- assert.equal(logs.length, 1);
+ assert.equal(logs.length >= 1, true);
assert.equal(logs[0].message.includes('Expected getImage() parameter'), true);
});
@@ -721,7 +721,7 @@ describe('astro:image', () => {
let res = await fixture.fetch('/get-image-undefined');
await res.text();
- assert.equal(logs.length, 1);
+ assert.equal(logs.length >= 1, true);
assert.equal(logs[0].message.includes('Expected `src` property'), true);
});
diff --git a/packages/astro/test/fixtures/rewrite-runtime-error-custom500/astro.config.mjs b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/astro.config.mjs
new file mode 100644
index 000000000..af13ef19b
--- /dev/null
+++ b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/astro.config.mjs
@@ -0,0 +1,9 @@
+import { defineConfig } from 'astro/config';
+
+// https://astro.build/config
+export default defineConfig({
+ experimental: {
+ rewriting: true
+ },
+ site: "https://example.com"
+});
diff --git a/packages/astro/test/fixtures/rewrite-runtime-error-custom500/package.json b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/package.json
new file mode 100644
index 000000000..6d844adc4
--- /dev/null
+++ b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/rewrite-runtime-errror-custom500",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/500.astro b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/500.astro
new file mode 100644
index 000000000..7df3fc458
--- /dev/null
+++ b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/500.astro
@@ -0,0 +1,4 @@
+---
+---
+
+<h1>I am the custom 500</h1>
diff --git a/packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/errors/start.astro b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/errors/start.astro
new file mode 100644
index 000000000..0fff9a7e7
--- /dev/null
+++ b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/errors/start.astro
@@ -0,0 +1,3 @@
+---
+return Astro.rewrite("/errors/throw")
+---
diff --git a/packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/errors/throw.astro b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/errors/throw.astro
new file mode 100644
index 000000000..1f879a9b4
--- /dev/null
+++ b/packages/astro/test/fixtures/rewrite-runtime-error-custom500/src/pages/errors/throw.astro
@@ -0,0 +1,3 @@
+---
+throw new Error("Fancy error")
+---
diff --git a/packages/astro/test/rewrite.test.js b/packages/astro/test/rewrite.test.js
index db2830608..d0d237639 100644
--- a/packages/astro/test/rewrite.test.js
+++ b/packages/astro/test/rewrite.test.js
@@ -314,7 +314,7 @@ describe('Middleware', () => {
});
});
-describe('Runtime error', () => {
+describe('Runtime error, default 500', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let devServer;
@@ -330,10 +330,83 @@ describe('Runtime error', () => {
await devServer.stop();
});
- it('should render the index page when navigating /reroute ', async () => {
- const html = await fixture.fetch('/errors/from').then((res) => res.text());
+ it('should return a 500 status code, but not render the custom 500', async () => {
+ const response = await fixture.fetch('/errors/from');
+ assert.equal(response.status, 500);
+ const text = await response.text();
+ assert.match(text, /@vite\/client/);
+ });
+});
+
+describe('Runtime error in SSR, default 500', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+ let app;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/rewrite-runtime-error/',
+ output: 'server',
+ adapter: testAdapter(),
+ });
+ await fixture.build();
+ app = await fixture.loadTestAdapterApp();
+ });
+
+ it('should return a 500 status code, but not render the custom 500', async () => {
+ const request = new Request('http://example.com/errors/from');
+ const response = await app.render(request);
+ const text = await response.text();
+ assert.equal(text, '');
+ });
+});
+
+describe('Runtime error in dev, custom 500', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+ let devServer;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/rewrite-runtime-error-custom500/',
+ });
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('should render the custom 500 when rewriting a page that throws an error', async () => {
+ const response = await fixture.fetch('/errors/start');
+ assert.equal(response.status, 500);
+ const html = await response.text();
+ assert.match(html, /I am the custom 500/);
+ });
+});
+
+describe('Runtime error in SSR, custom 500', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+ let app;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/rewrite-runtime-error-custom500/',
+ output: 'server',
+ adapter: testAdapter(),
+ });
+ await fixture.build();
+ app = await fixture.loadTestAdapterApp();
+ });
+
+ it('should render the custom 500 when rewriting a page that throws an error', async () => {
+ const request = new Request('http://example.com/errors/start');
+ const response = await app.render(request);
+ const html = await response.text();
+
const $ = cheerioLoad(html);
- assert.equal($('title').text(), 'Error');
+ assert.equal($('h1').text(), 'I am the custom 500');
});
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a47a30e5b..ef034d50c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3493,6 +3493,12 @@ importers:
specifier: workspace:*
version: link:../../..
+ packages/astro/test/fixtures/rewrite-runtime-error-custom500:
+ dependencies:
+ astro:
+ specifier: workspace:*
+ version: link:../../..
+
packages/astro/test/fixtures/rewrite-server:
dependencies:
astro: