feat: Vike +data 服务端取数 / SEO Head 移植 / 404 错误页 / 服务端 DOM 模拟
- feedData.ts 聚合首页/板块/帖子详情数据,缺失内容 throw render(404) - Go 侧 spa_meta/seo 的 head 生成逻辑移植到 src/ssr/head.ts - _error 页复用站点壳渲染 404,+data 从 abortReason 恢复壳层数据 - linkedom 模拟服务端 DOM,DOMPurify 封装为 SSR 透传的 sanitizeHtml - 新增 /api/home-boot 聚合端点(handler/home_boot.go)供 SSR 冷启动灌缓存
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import type { Config } from 'vike/types';
|
||||
|
||||
export default {
|
||||
// pageProps 透传给客户端(boot 数据经 +data 的 pageContext.data 自动序列化)
|
||||
passToClient: ['pageProps'],
|
||||
// pageProps / data(+data 返回的 boot / snapshot / head)透传给客户端供 hydrate seed
|
||||
passToClient: ['pageProps', 'data'],
|
||||
// 自定义集成(无 vike-react):声明 `ssr` 配置供页面 +ssr.ts 使用(SPA 壳页 ssr: false)
|
||||
meta: {
|
||||
ssr: {
|
||||
env: { server: true },
|
||||
},
|
||||
},
|
||||
} satisfies Config;
|
||||
|
||||
10
frontend/pages/_error/+Page.tsx
Normal file
10
frontend/pages/_error/+Page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import App from '../../src/App';
|
||||
import type { PageContext } from 'vike/types';
|
||||
|
||||
export { Page };
|
||||
|
||||
/** 错误页:复用 App 壳层与路由树 —— /post/:id 未找到渲染帖子自己的未找到态,未知路径渲染 catch-all NotFoundPage */
|
||||
function Page(pageContext: PageContext & { url?: string }) {
|
||||
const url = pageContext.url ?? pageContext.urlOriginal ?? '/';
|
||||
return <App url={url} />;
|
||||
}
|
||||
24
frontend/pages/_error/+data.ts
Normal file
24
frontend/pages/_error/+data.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { PageContextServer } from 'vike/types';
|
||||
import type { FeedData } from '../../src/ssr/feedData';
|
||||
|
||||
export { data };
|
||||
|
||||
type NotFoundAbortReason = {
|
||||
head?: FeedData['head'];
|
||||
boot?: FeedData['boot'];
|
||||
};
|
||||
|
||||
/**
|
||||
* 错误页取数:feed +data 未找到(帖子/板块)时 throw render(404, { head, boot }),
|
||||
* 壳层数据经 abortReason 带过来,保证 404 页也渲染完整站点壳与 noindex head。
|
||||
*/
|
||||
async function data(pageContext: PageContextServer): Promise<FeedData> {
|
||||
if (!import.meta.env.SSR) return { kind: 'notfound' };
|
||||
|
||||
const reason = pageContext.abortReason as NotFoundAbortReason | undefined;
|
||||
return {
|
||||
kind: 'notfound',
|
||||
head: reason?.head,
|
||||
boot: reason?.boot,
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import type { PageContext } from 'vike/types';
|
||||
export { Page };
|
||||
|
||||
/** SSR feed 页:服务端渲染整棵 react-router 树,客户端 hydrate 后接管导航 */
|
||||
function Page(pageContext: PageContext) {
|
||||
const { pathname, searchOriginal } = pageContext.urlParsed;
|
||||
return <App url={pathname + (searchOriginal ?? '')} />;
|
||||
function Page(pageContext: PageContext & { url?: string }) {
|
||||
// onRenderHtml 透传 url(pathname + search);urlOriginal 兜底(含 query)
|
||||
const url = pageContext.url ?? pageContext.urlOriginal ?? '/';
|
||||
return <App url={url} />;
|
||||
}
|
||||
|
||||
9
frontend/pages/feed/+data.ts
Normal file
9
frontend/pages/feed/+data.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { PageContextServer } from 'vike/types';
|
||||
import { loadFeedData } from '../../src/ssr/feedData';
|
||||
|
||||
export { data };
|
||||
|
||||
// SSR 白名单页(/ 、/board/:id 、/post/:id)服务端取数 + head 构建
|
||||
async function data(pageContext: PageContextServer) {
|
||||
return await loadFeedData(pageContext);
|
||||
}
|
||||
Reference in New Issue
Block a user