aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/ninety-snails-study.md5
-rw-r--r--packages/astro/src/vite-plugin-astro-server/route.ts14
-rw-r--r--packages/astro/test/custom-404-html.test.js42
-rw-r--r--packages/astro/test/custom-404-injected.test.js42
-rw-r--r--packages/astro/test/fixtures/custom-404-html/astro.config.mjs4
-rw-r--r--packages/astro/test/fixtures/custom-404-html/package.json8
-rw-r--r--packages/astro/test/fixtures/custom-404-html/src/pages/404.html9
-rw-r--r--packages/astro/test/fixtures/custom-404-html/src/pages/index.astro11
-rw-r--r--packages/astro/test/fixtures/custom-404-injected/astro.config.mjs18
-rw-r--r--packages/astro/test/fixtures/custom-404-injected/package.json8
-rw-r--r--packages/astro/test/fixtures/custom-404-injected/src/404.astro13
-rw-r--r--packages/astro/test/fixtures/custom-404-injected/src/pages/index.astro11
-rw-r--r--packages/astro/test/units/dev/dev.test.js62
-rw-r--r--pnpm-lock.yaml12
14 files changed, 250 insertions, 9 deletions
diff --git a/.changeset/ninety-snails-study.md b/.changeset/ninety-snails-study.md
new file mode 100644
index 000000000..84bf956b7
--- /dev/null
+++ b/.changeset/ninety-snails-study.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Support custom 404s added via `injectRoute` or as `src/pages/404.html`
diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts
index 79232f10f..da280f7e1 100644
--- a/packages/astro/src/vite-plugin-astro-server/route.ts
+++ b/packages/astro/src/vite-plugin-astro-server/route.ts
@@ -1,6 +1,6 @@
import type http from 'http';
import mime from 'mime';
-import type { AstroSettings, ComponentInstance, ManifestData, RouteData } from '../@types/astro';
+import type { ComponentInstance, ManifestData, RouteData } from '../@types/astro';
import type {
ComponentPreload,
DevelopmentEnvironment,
@@ -12,12 +12,10 @@ import { call as callEndpoint } from '../core/endpoint/dev/index.js';
import { throwIfRedirectNotAllowed } from '../core/endpoint/index.js';
import { AstroErrorData } from '../core/errors/index.js';
import { warn } from '../core/logger/core.js';
-import { appendForwardSlash } from '../core/path.js';
import { preload, renderPage } from '../core/render/dev/index.js';
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/index.js';
import { createRequest } from '../core/request.js';
import { matchAllRoutes } from '../core/routing/index.js';
-import { resolvePages } from '../core/util.js';
import { log404 } from './common.js';
import { handle404Response, writeSSRResult, writeWebResponse } from './response.js';
@@ -35,11 +33,9 @@ interface MatchedRoute {
mod: ComponentInstance;
}
-function getCustom404Route({ config }: AstroSettings, manifest: ManifestData) {
- // For Windows compat, use relative page paths to match the 404 route
- const relPages = resolvePages(config).href.replace(config.root.href, '');
- const pattern = new RegExp(`${appendForwardSlash(relPages)}404.(astro|md)`);
- return manifest.routes.find((r) => r.component.match(pattern));
+function getCustom404Route(manifest: ManifestData): RouteData | undefined {
+ const route404 = /^\/404\/?$/;
+ return manifest.routes.find((r) => route404.test(r.route));
}
export async function matchRoute(
@@ -97,7 +93,7 @@ export async function matchRoute(
}
log404(logging, pathname);
- const custom404 = getCustom404Route(settings, manifest);
+ const custom404 = getCustom404Route(manifest);
if (custom404) {
const filePath = new URL(`./${custom404.component}`, settings.config.root);
diff --git a/packages/astro/test/custom-404-html.test.js b/packages/astro/test/custom-404-html.test.js
new file mode 100644
index 000000000..6c3ac6dec
--- /dev/null
+++ b/packages/astro/test/custom-404-html.test.js
@@ -0,0 +1,42 @@
+import { expect } from 'chai';
+import * as cheerio from 'cheerio';
+import { loadFixture } from './test-utils.js';
+
+describe('Custom 404.html', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/custom-404-html/',
+ site: 'http://example.com',
+ });
+ });
+
+ describe('dev', () => {
+ let devServer;
+ let $;
+
+ before(async () => {
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('renders /', async () => {
+ const html = await fixture.fetch('/').then((res) => res.text());
+ $ = cheerio.load(html);
+
+ expect($('h1').text()).to.equal('Home');
+ });
+
+ it('renders 404 for /a', async () => {
+ const html = await fixture.fetch('/a').then((res) => res.text());
+ $ = cheerio.load(html);
+
+ expect($('h1').text()).to.equal('Page not found');
+ expect($('p').text()).to.equal('This 404 is a static HTML file.');
+ });
+ });
+});
diff --git a/packages/astro/test/custom-404-injected.test.js b/packages/astro/test/custom-404-injected.test.js
new file mode 100644
index 000000000..c8963243a
--- /dev/null
+++ b/packages/astro/test/custom-404-injected.test.js
@@ -0,0 +1,42 @@
+import { expect } from 'chai';
+import * as cheerio from 'cheerio';
+import { loadFixture } from './test-utils.js';
+
+describe('Custom 404 with injectRoute', () => {
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/custom-404-injected/',
+ site: 'http://example.com',
+ });
+ });
+
+ describe('dev', () => {
+ let devServer;
+ let $;
+
+ before(async () => {
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('renders /', async () => {
+ const html = await fixture.fetch('/').then((res) => res.text());
+ $ = cheerio.load(html);
+
+ expect($('h1').text()).to.equal('Home');
+ });
+
+ it('renders 404 for /a', async () => {
+ const html = await fixture.fetch('/a').then((res) => res.text());
+ $ = cheerio.load(html);
+
+ expect($('h1').text()).to.equal('Page not found');
+ expect($('p').text()).to.equal('/a');
+ });
+ });
+});
diff --git a/packages/astro/test/fixtures/custom-404-html/astro.config.mjs b/packages/astro/test/fixtures/custom-404-html/astro.config.mjs
new file mode 100644
index 000000000..882e6515a
--- /dev/null
+++ b/packages/astro/test/fixtures/custom-404-html/astro.config.mjs
@@ -0,0 +1,4 @@
+import { defineConfig } from 'astro/config';
+
+// https://astro.build/config
+export default defineConfig({});
diff --git a/packages/astro/test/fixtures/custom-404-html/package.json b/packages/astro/test/fixtures/custom-404-html/package.json
new file mode 100644
index 000000000..b137ab076
--- /dev/null
+++ b/packages/astro/test/fixtures/custom-404-html/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/custom-404-html",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/custom-404-html/src/pages/404.html b/packages/astro/test/fixtures/custom-404-html/src/pages/404.html
new file mode 100644
index 000000000..50051bbc0
--- /dev/null
+++ b/packages/astro/test/fixtures/custom-404-html/src/pages/404.html
@@ -0,0 +1,9 @@
+<html lang="en">
+<head>
+ <title>Not Found - Custom 404</title>
+</head>
+<body>
+ <h1>Page not found</h1>
+ <p>This 404 is a static HTML file.</p>
+</body>
+</html>
diff --git a/packages/astro/test/fixtures/custom-404-html/src/pages/index.astro b/packages/astro/test/fixtures/custom-404-html/src/pages/index.astro
new file mode 100644
index 000000000..cf5ef9b58
--- /dev/null
+++ b/packages/astro/test/fixtures/custom-404-html/src/pages/index.astro
@@ -0,0 +1,11 @@
+---
+---
+
+<html lang="en">
+<head>
+ <title>Custom 404</title>
+</head>
+<body>
+ <h1>Home</h1>
+</body>
+</html>
diff --git a/packages/astro/test/fixtures/custom-404-injected/astro.config.mjs b/packages/astro/test/fixtures/custom-404-injected/astro.config.mjs
new file mode 100644
index 000000000..d46ce7eb7
--- /dev/null
+++ b/packages/astro/test/fixtures/custom-404-injected/astro.config.mjs
@@ -0,0 +1,18 @@
+import { defineConfig } from 'astro/config';
+
+// https://astro.build/config
+export default defineConfig({
+ integrations: [
+ {
+ name: '404-integration',
+ hooks: {
+ 'astro:config:setup': ({ injectRoute }) => {
+ injectRoute({
+ pattern: '404',
+ entryPoint: 'src/404.astro',
+ });
+ },
+ },
+ },
+ ],
+});
diff --git a/packages/astro/test/fixtures/custom-404-injected/package.json b/packages/astro/test/fixtures/custom-404-injected/package.json
new file mode 100644
index 000000000..855088a9f
--- /dev/null
+++ b/packages/astro/test/fixtures/custom-404-injected/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/custom-404-injected",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/custom-404-injected/src/404.astro b/packages/astro/test/fixtures/custom-404-injected/src/404.astro
new file mode 100644
index 000000000..63d560b0f
--- /dev/null
+++ b/packages/astro/test/fixtures/custom-404-injected/src/404.astro
@@ -0,0 +1,13 @@
+---
+const canonicalURL = new URL(Astro.url.pathname, Astro.site);
+---
+
+<html lang="en">
+<head>
+ <title>Not Found - Custom 404</title>
+</head>
+<body>
+ <h1>Page not found</h1>
+ <p>{canonicalURL.pathname}</p>
+</body>
+</html>
diff --git a/packages/astro/test/fixtures/custom-404-injected/src/pages/index.astro b/packages/astro/test/fixtures/custom-404-injected/src/pages/index.astro
new file mode 100644
index 000000000..cf5ef9b58
--- /dev/null
+++ b/packages/astro/test/fixtures/custom-404-injected/src/pages/index.astro
@@ -0,0 +1,11 @@
+---
+---
+
+<html lang="en">
+<head>
+ <title>Custom 404</title>
+</head>
+<body>
+ <h1>Home</h1>
+</body>
+</html>
diff --git a/packages/astro/test/units/dev/dev.test.js b/packages/astro/test/units/dev/dev.test.js
index eddc14c5d..5c19af635 100644
--- a/packages/astro/test/units/dev/dev.test.js
+++ b/packages/astro/test/units/dev/dev.test.js
@@ -158,6 +158,68 @@ describe('dev container', () => {
);
});
+ it('Serves injected 404 route for any 404', async () => {
+ const fs = createFs(
+ {
+ '/src/components/404.astro': `<h1>Custom 404</h1>`,
+ '/src/pages/page.astro': `<h1>Regular page</h1>`,
+ },
+ root
+ );
+
+ await runInContainer(
+ {
+ fs,
+ root,
+ userConfig: {
+ output: 'server',
+ integrations: [
+ {
+ name: '@astrojs/test-integration',
+ hooks: {
+ 'astro:config:setup': ({ injectRoute }) => {
+ injectRoute({
+ pattern: '/404',
+ entryPoint: './src/components/404.astro',
+ });
+ },
+ },
+ },
+ ],
+ },
+ },
+ async (container) => {
+ {
+ // Regular pages are served as expected.
+ const r = createRequestAndResponse({ method: 'GET', url: '/page' });
+ container.handle(r.req, r.res);
+ await r.done;
+ const doc = await r.text();
+ expect(doc).to.match(/<h1>Regular page<\/h1>/);
+ expect(r.res.statusCode).to.equal(200);
+ }
+ {
+ // `/404` serves the custom 404 page as expected.
+ const r = createRequestAndResponse({ method: 'GET', url: '/404' });
+ container.handle(r.req, r.res);
+ await r.done;
+ const doc = await r.text();
+ expect(doc).to.match(/<h1>Custom 404<\/h1>/);
+ expect(r.res.statusCode).to.equal(200);
+ }
+ {
+ // A non-existent page also serves the custom 404 page.
+ const r = createRequestAndResponse({ method: 'GET', url: '/other-page' });
+ container.handle(r.req, r.res);
+ await r.done;
+ const doc = await r.text();
+ expect(doc).to.match(/<h1>Custom 404<\/h1>/);
+ expect(r.res.statusCode).to.equal(200);
+ }
+ }
+ );
+ });
+
it('items in public/ are not available from root when using a base', async () => {
await runInContainer(
{
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d5b5ba430..55576bdb1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2438,6 +2438,18 @@ importers:
specifier: workspace:*
version: link:../../..
+ packages/astro/test/fixtures/custom-404-html:
+ dependencies:
+ astro:
+ specifier: workspace:*
+ version: link:../../..
+
+ packages/astro/test/fixtures/custom-404-injected:
+ dependencies:
+ astro:
+ specifier: workspace:*
+ version: link:../../..
+
packages/astro/test/fixtures/custom-404-md:
dependencies:
astro: