From 485f6469e35b8650da008e154e4e61d04b986a7e Mon Sep 17 00:00:00 2001 From: Kevin Deng Date: Thu, 20 Aug 2026 15:47:19 +0900 Subject: [PATCH] Prerender all canonical character pages without alias files (#21) --- README.md | 4 ++-- nuxt.config.ts | 44 +++++++++----------------------------------- public/_redirects | 7 +++---- 3 files changed, 14 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 1637227..500ddf2 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ pnpm test pnpm generate # 静态站点 ``` -每一行的地址是它的行名(`/char/着`)。五地展示形、`aka` 和 `alternatives` 也可作为地址,302 跳到所属的行——例如 `/char/国`、`/char/郞`、`/char/缐`,页面用 `rel=canonical` 指回行名地址;未确认关系不是地址别名。 +每一行的地址是它的行名(`/char/着`)。五地展示形、`aka` 和 `alternatives` 也可作为地址,由客户端跳到所属的行——例如 `/char/国`、`/char/郞`、`/char/缐`,页面用 `rel=canonical` 指回行名地址;未确认关系不是地址别名。 字表 `public/data/chars.json` 提交在仓库里,也是站点的开放数据地址 `/data/chars.json`。字体子集约 12MB,**不提交**,由 `pnpm build:data` 生成——所以构建前必须先跑一次。原始下载缓存在 `data/raw/` 下按类别存放(`charlist/`、`opencc/`、`cmap/`、`font/`、`unihan/`、`frequency/`),已 gitignore。 @@ -78,7 +78,7 @@ pnpm generate # 静态站点 | 输出目录 | `.output/public` | | Node 版本 | LTS(`.node-version`) | -`public/_redirects` 把没有预渲染的单字页交回给应用(静态文件优先,约 2,300 个常见单字及别名地址仍按文件直出),`public/_headers` 给带内容哈希的 `_nuxt/*` 设了长缓存。 +全部 8,449 个字组详情页都会生成独立 HTML;页面数据在本地 bundle 中,因此关闭了每路由额外生成 `_payload.json` 的 payload extraction。地区异体别名也不另外生成跳转页,而是通过 `public/_redirects` 回到应用后由客户端跳转。同一条回退规则也让应用显示未知字符的 404;`public/_headers` 给带内容哈希的 `_nuxt/*` 设了长缓存。 构建时 `pnpm build:data` 会下载约 **211 MB** 原始数据(其中 195 MB 是十份 Noto CJK 字体),CI 上每次构建都要重下。若嫌慢,可改为本地构建后直传: diff --git a/nuxt.config.ts b/nuxt.config.ts index 4dba075..038da58 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,48 +1,18 @@ import { readFileSync } from 'node:fs' -import { REGIONS, type CharsData } from './shared/types.ts' +import type { CharsData } from './shared/types.ts' import type { NuxtConfig } from 'nuxt/schema' /** - * Character pages worth prerendering: those a reader is most likely to land on - * from a search. Rendering all 8,000 would put hundreds of MB of near-identical - * HTML in the output for no gain; the rest are served by the SPA fallback. + * Every canonical row has a real static detail page. Aliases deliberately use + * the SPA fallback and redirect in char-alias middleware instead: emitting a + * tiny file for each alias would consume thousands of the host's file quota. */ const chars: CharsData = JSON.parse( readFileSync(new URL('public/data/chars.json', import.meta.url), 'utf8'), ) -const PRERENDERED_KEYS = new Set( - chars.rows - .filter((row) => row.common === REGIONS.length) - .map((row) => row.key), -) -const KEYS = new Set(chars.rows.map((row) => row.key)) - -/** - * The regional forms redirect to the row they belong to (/char/国 -> - * /char/國). An alias is prerendered when its target is, so a crawler that - * finds one of these addresses gets the redirect from the host rather than - * having to run the app to be told. - */ -const ALIASES = new Map() -for (const row of chars.rows) - for (const char of [ - ...row.chars, - row.old?.char ?? '', - ...(row.aka ?? []), - ...REGIONS.flatMap((region) => - (row.alternatives?.[region] ?? []).map((entry) => entry.char), - ), - ]) - if (char && !KEYS.has(char) && !ALIASES.has(char)) - ALIASES.set(char, row.key) const path = (key: string) => `/char/${encodeURIComponent(key)}` -const PRERENDERED_CHARS = [ - ...[...PRERENDERED_KEYS].map(path), - ...[...ALIASES] - .filter(([, target]) => PRERENDERED_KEYS.has(target)) - .map(([alias]) => path(alias)), -] +const PRERENDERED_CHARS = chars.rows.map((row) => path(row.key)) // https://nuxt.com/docs/api/configuration/nuxt-config export default { @@ -53,6 +23,10 @@ export default { }, experimental: { + // Page data is bundled locally rather than loaded with useAsyncData, so + // the extracted payload for every route is just an empty 69-byte file. + // Keep hydration state inline and avoid spending one host file per page. + payloadExtraction: false, // Install Nuxt's View Transition integration. app.viewTransition below // leaves it off until the global route middleware opts in per navigation. viewTransition: true, diff --git a/public/_redirects b/public/_redirects index 98b6313..a81f971 100644 --- a/public/_redirects +++ b/public/_redirects @@ -1,5 +1,4 @@ -# Only the 2,064 characters common to all four places are prerendered; the rest -# are served by the app itself, which also resolves the regional forms -# (/char/国 -> /char/國) and raises its own 404. Static files win over this -# rule, so every prerendered page is still served as a file. +# Canonical character pages are prerendered. Regional aliases use this fallback +# so the client can redirect them; unknown characters use it for the app's 404. +# Static files win, so generated detail pages are still served with their HTML. /char/* /200 200 -- 2.51.2