Skip to content

Commit 7a14095

Browse files
authored
Merge pull request #44 from Hoshivel/fix/serve-404-page-hoshivel-web
把 404 頁接上未匹配的路徑
2 parents 1cdfcab + 94063a8 commit 7a14095

4 files changed

Lines changed: 122 additions & 1 deletion

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
dist/
33
# generated types
44
.astro/
5+
# `npx wrangler dev` 的本機狀態。本機不安裝 wrangler(見 wrangler.jsonc 開頭),
6+
# 但要親眼確認未匹配路徑真的拿得到 404 頁時,只有它跑得起 Workers 的資產路由。
7+
.wrangler/
58

69
# 字體子集化的原始檔快取(scripts/subset-fonts.py 會自行下載)
710
scripts/.fontcache/

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ Cloudflare 建置:`npm run build` → `npx wrangler deploy`。建置設定在
7979
- `wrangler.jsonc``npx wrangler deploy` 讀取,在 Cloudflare 的建置環境執行。
8080
本機不需要 wrangler,因此不在 dependencies,也沒有 `deploy` script。
8181
- 本站不落在任何節點上:不進 hoshi-deploy inventory,origin 憑證不含 `hoshivel.com`
82+
- 未匹配的路徑由 `assets.not_found_handling` 交給 `dist/404.html`。這個鍵的預設值
83+
`"none"` 回的是**零位元組**的 404——`src/pages/404.astro` 存在、`astro build`
84+
也產得出 `dist/404.html`,兩者都不代表它被服務。要親眼確認就在本機跑一次
85+
Workers 的資產路由(一次性,不加進 dependencies):
86+
87+
```sh
88+
npx astro build
89+
npx wrangler dev --port 26829
90+
curl -sS -i http://127.0.0.1:26829/zzz-not-here
91+
```
92+
93+
要看到 `404` 後面**接著一份 HTML**。空白的 body 會被瀏覽器畫成白頁、分頁標題
94+
退回原始 URL,看起來像整個站掛了,而不是路徑打錯。
8295

8396
`hoshi build` 產生 `dist/` 靜態檔。**不得**加入 SSR adapter、server/hybrid
8497
output、Node runtime 或容器產物——部署的是純靜態資產。

test/not-found.test.mjs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
The 404 page only exists if something serves it.
3+
4+
`src/pages/404.astro` was here from early on and `astro build` has always
5+
written `dist/404.html`, yet every unmatched path on hoshivel.com returned a
6+
404 with a ZERO-BYTE body until 2026-09-03. The gap was one key in
7+
`wrangler.jsonc`: `assets.not_found_handling` defaults to `"none"`, which
8+
answers unmatched requests with an empty response and never opens `404.html`.
9+
10+
A browser paints an empty body as a blank white page and falls back to the raw
11+
URL for the tab title, so the failure looks like a crashed site rather than a
12+
typo. The sister site sr-web had the identical defect and the identical fix;
13+
see the workspace decision
14+
`decisions/infrastructure/Workers-靜態站要顯式開啟-404-頁.md`.
15+
16+
Nothing in the build catches this: the page compiles, the asset is emitted, the
17+
deploy succeeds. Only these assertions stand between the config and a silent
18+
return to the blank page.
19+
*/
20+
import assert from "node:assert/strict";
21+
import { readFileSync } from "node:fs";
22+
import test from "node:test";
23+
24+
import { ui as dictionaries } from "../src/i18n/ui.ts";
25+
26+
const read = (path) => readFileSync(new URL(path, import.meta.url), "utf8");
27+
28+
/**
29+
* Strip JSONC comments without touching comment-like text inside strings, so a
30+
* value such as an "https://" URL cannot truncate the parse.
31+
*/
32+
const parseJsonc = (source) => {
33+
let out = "";
34+
let inString = false;
35+
let escaped = false;
36+
for (let i = 0; i < source.length; i += 1) {
37+
const ch = source[i];
38+
if (inString) {
39+
out += ch;
40+
if (escaped) escaped = false;
41+
else if (ch === "\\") escaped = true;
42+
else if (ch === '"') inString = false;
43+
continue;
44+
}
45+
if (ch === '"') {
46+
inString = true;
47+
out += ch;
48+
continue;
49+
}
50+
if (ch === "/" && source[i + 1] === "/") {
51+
while (i < source.length && source[i] !== "\n") i += 1;
52+
out += "\n";
53+
continue;
54+
}
55+
if (ch === "/" && source[i + 1] === "*") {
56+
i += 2;
57+
while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i += 1;
58+
i += 1;
59+
continue;
60+
}
61+
out += ch;
62+
}
63+
return JSON.parse(out);
64+
};
65+
66+
const wrangler = parseJsonc(read("../wrangler.jsonc"));
67+
const astroConfig = read("../astro.config.mjs");
68+
const page = read("../src/pages/404.astro");
69+
70+
test("unmatched paths are answered with the 404 page, not an empty body", () => {
71+
assert.equal(
72+
wrangler.assets?.not_found_handling,
73+
"404-page",
74+
'assets.not_found_handling must be "404-page"; the default "none" returns a zero-byte 404',
75+
);
76+
});
77+
78+
test("the asset directory Workers serves is the one Astro builds into", () => {
79+
// The 404 wiring is worth nothing if it points at a directory the build never
80+
// fills. Astro's outDir is left at its default here, so `dist` is the contract
81+
// between the two files — assert the config has not quietly moved.
82+
assert.match(wrangler.assets?.directory ?? "", /^\.\/dist\/?$/);
83+
assert.doesNotMatch(astroConfig, /outDir/, "astro outDir moved; wrangler assets.directory must follow");
84+
});
85+
86+
test("the 404 page draws its copy from the shared dictionary", () => {
87+
// Hardcoded strings here drift away from src/i18n/ui.ts on the next copy pass,
88+
// and this page is the one nobody opens on purpose.
89+
for (const key of ["notfound.title", "notfound.body", "notfound.back"]) {
90+
assert.ok(page.includes(`"${key}"`), `404.astro must use ${key}`);
91+
for (const [locale, dictionary] of Object.entries(dictionaries)) {
92+
assert.ok(dictionary[key]?.trim(), `${locale}/${key} must not be empty`);
93+
}
94+
}
95+
});
96+
97+
test("the 404 page links back into the site", () => {
98+
assert.match(page, /href="\/"/, "404.astro must offer a link home");
99+
});

wrangler.jsonc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
"name": "hoshivel-web",
77
"compatibility_date": "2026-08-10",
88
"assets": {
9-
"directory": "./dist"
9+
"directory": "./dist",
10+
// 未匹配的路徑要拿到 `dist/404.html`。這個鍵的預設值是 `"none"`,而 `"none"`
11+
// 回的是一份**零位元組**的 404:瀏覽器把空 body 畫成白頁、分頁標題退回原始
12+
// URL,看起來就像站掛了。`src/pages/404.astro` 在、`dist/404.html` 也一直
13+
// 建置得出來,缺的只有這一行——三者單獨看都正常,症狀卻指向別處。
14+
// 理由見 workspace 的 decisions/infrastructure/Workers-靜態站要顯式開啟-404-頁.md。
15+
"not_found_handling": "404-page"
1016
}
1117
}

0 commit comments

Comments
 (0)