Skip to content

Commit 9869f2f

Browse files
authored
can jump 404 when that page does not exist (#5701)
1 parent 21a55b3 commit 9869f2f

11 files changed

Lines changed: 99 additions & 9 deletions

File tree

.changeset/red-snakes-fetch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/node': patch
3+
---
4+
5+
Support custom 404 page in standalone mode

packages/integrations/node/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"astro": "workspace:*",
4242
"astro-scripts": "workspace:*",
4343
"chai": "^4.3.6",
44+
"cheerio": "^1.0.0-rc.11",
4445
"mocha": "^9.2.2",
4546
"node-mocks-http": "^1.11.0",
4647
"undici": "^5.14.0"

packages/integrations/node/src/middleware.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import type { NodeApp } from 'astro/app/node';
22
import type { IncomingMessage, ServerResponse } from 'http';
33
import type { Readable } from 'stream';
44
import { responseIterator } from './response-iterator';
5+
import type { Options } from './types';
56

6-
export default function (app: NodeApp) {
7+
export default function (app: NodeApp, mode: Options['mode']) {
78
return async function (
89
req: IncomingMessage,
910
res: ServerResponse,
1011
next?: (err?: unknown) => void
1112
) {
1213
try {
13-
const route = app.match(req);
14-
14+
const route =
15+
mode === 'standalone' ? app.match(req, { matchNotFound: true }) : app.match(req);
1516
if (route) {
1617
try {
1718
const response = await app.render(req);

packages/integrations/node/src/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ polyfill(globalThis, {
99
exclude: 'window document',
1010
});
1111

12-
export function createExports(manifest: SSRManifest) {
12+
export function createExports(manifest: SSRManifest, options: Options) {
1313
const app = new NodeApp(manifest);
1414
return {
15-
handler: middleware(app),
15+
handler: middleware(app, options.mode),
1616
};
1717
}
1818

packages/integrations/node/src/standalone.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function getResolvedHostForHttpServer(host: string | boolean) {
3737
export default function startServer(app: NodeApp, options: Options) {
3838
const port = process.env.PORT ? Number(process.env.PORT) : options.port ?? 8080;
3939
const { client } = resolvePaths(options);
40-
const handler = middleware(app);
40+
const handler = middleware(app, options.mode);
4141

4242
// Allow to provide host value at runtime
4343
const host = getResolvedHostForHttpServer(

packages/integrations/node/test/api-route.test.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import nodejs from '../dist/index.js';
2-
import { loadFixture, createRequestAndResponse, toPromise } from './test-utils.js';
2+
import { loadFixture, createRequestAndResponse } from './test-utils.js';
33
import { expect } from 'chai';
44

55
describe('API routes', () => {
@@ -17,18 +17,21 @@ describe('API routes', () => {
1717

1818
it('Can get the request body', async () => {
1919
const { handler } = await import('./fixtures/api-route/dist/server/entry.mjs');
20-
2120
let { req, res, done } = createRequestAndResponse({
2221
method: 'POST',
2322
url: '/recipes',
2423
});
2524

2625
handler(req, res);
27-
req.send(JSON.stringify({ id: 2 }));
2826

27+
req.send(JSON.stringify({ id: 2 }));
28+
2929
let [buffer] = await done;
30+
3031
let json = JSON.parse(buffer.toString('utf-8'));
32+
3133
expect(json.length).to.equal(1);
34+
3235
expect(json[0].name).to.equal('Broccoli Soup');
3336
});
3437

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "@test/node-middleware",
3+
"version": "0.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"astro": "workspace:*",
7+
"@astrojs/node": "workspace:*"
8+
}
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
---
3+
4+
<!DOCTYPE html>
5+
<html lang="en">
6+
<head>
7+
<meta charset="UTF-8">
8+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
9+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
10+
<title>404</title>
11+
</head>
12+
<body><h1>404!!!!!!!!!!</h1></body>
13+
</html>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
---
3+
4+
<html lang="en">
5+
<head><title>node-middleware</title></head>
6+
<style>
7+
</style>
8+
<body>
9+
<div>1</div>
10+
</body>
11+
</html>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import nodejs from '../dist/index.js';
2+
import { loadFixture , createRequestAndResponse} from './test-utils.js';
3+
import { expect } from 'chai';
4+
import * as cheerio from 'cheerio';
5+
6+
describe('test 404 cant load', () => {
7+
let fixture;
8+
before(async () => {
9+
fixture = await loadFixture({
10+
root: './fixtures/node-middleware/',
11+
output: 'server',
12+
adapter: nodejs({ mode: 'standalone' }),
13+
});
14+
await fixture.build();
15+
});
16+
describe('test 404', async () => {
17+
let devPreview;
18+
19+
before(async () => {
20+
devPreview = await fixture.preview();
21+
});
22+
after(async () => {
23+
await devPreview.stop();
24+
});
25+
it('when mode is standalone', async () => {
26+
const res = await fixture.fetch('/error-page');
27+
28+
expect(res.status).to.equal(404);
29+
30+
const html = await res.text();
31+
const $ = cheerio.load(html);
32+
33+
const h1 = $('h1');
34+
expect(h1.text()).to.equal('404!!!!!!!!!!');
35+
});
36+
})
37+
})

0 commit comments

Comments
 (0)