@modern-js/main-doc 0.0.0-nightly-20240828170710 → 0.0.0-nightly-20240830170740
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/en/apis/app/runtime/core/use-loader.mdx +1 -1
- package/docs/en/components/ssr-monitor.mdx +3 -0
- package/docs/en/configure/app/output/ssg.mdx +52 -141
- package/docs/en/guides/advanced-features/_meta.json +0 -7
- package/docs/en/guides/basic-features/_meta.json +7 -1
- package/docs/en/guides/basic-features/data/data-fetch.mdx +134 -235
- package/docs/en/guides/basic-features/data/data-write.mdx +66 -77
- package/docs/en/guides/basic-features/render/_meta.json +1 -0
- package/docs/en/guides/basic-features/render/ssg.mdx +208 -0
- package/docs/en/guides/{advanced-features/ssr/cache.mdx → basic-features/render/ssr-cache.mdx} +38 -50
- package/docs/en/guides/basic-features/render/ssr.mdx +301 -0
- package/docs/en/guides/basic-features/render/streaming-ssr.mdx +230 -0
- package/docs/en/guides/basic-features/routes.mdx +275 -263
- package/docs/en/guides/concept/entries.mdx +9 -2
- package/docs/zh/apis/app/runtime/core/use-loader.mdx +1 -1
- package/docs/zh/components/ssr-monitor.mdx +3 -0
- package/docs/zh/configure/app/output/ssg.mdx +49 -139
- package/docs/zh/guides/advanced-features/_meta.json +0 -7
- package/docs/zh/guides/basic-features/_meta.json +7 -1
- package/docs/zh/guides/basic-features/data/data-fetch.mdx +96 -211
- package/docs/zh/guides/basic-features/data/data-write.mdx +54 -55
- package/docs/zh/guides/basic-features/render/_meta.json +1 -0
- package/docs/zh/guides/basic-features/render/ssg.mdx +210 -0
- package/docs/zh/guides/{advanced-features/ssr/cache.mdx → basic-features/render/ssr-cache.mdx} +16 -26
- package/docs/zh/guides/basic-features/render/ssr.mdx +309 -0
- package/docs/zh/guides/{advanced-features/ssr/stream.mdx → basic-features/render/streaming-ssr.mdx} +22 -37
- package/docs/zh/guides/basic-features/routes.mdx +252 -237
- package/docs/zh/guides/concept/entries.mdx +6 -3
- package/package.json +6 -6
- package/docs/en/guides/advanced-features/ssg.mdx +0 -116
- package/docs/en/guides/advanced-features/ssr/_meta.json +0 -1
- package/docs/en/guides/advanced-features/ssr/index.mdx +0 -23
- package/docs/en/guides/advanced-features/ssr/stream.mdx +0 -248
- package/docs/en/guides/advanced-features/ssr/usage.mdx +0 -341
- package/docs/en/guides/advanced-features/ssr.mdx +0 -555
- package/docs/zh/guides/advanced-features/ssg.mdx +0 -116
- package/docs/zh/guides/advanced-features/ssr/_meta.json +0 -1
- package/docs/zh/guides/advanced-features/ssr/index.mdx +0 -23
- package/docs/zh/guides/advanced-features/ssr/usage.mdx +0 -329
@@ -0,0 +1,309 @@
|
|
1
|
+
---
|
2
|
+
title: 服务端渲染(SSR)
|
3
|
+
---
|
4
|
+
|
5
|
+
# 服务端渲染(SSR)
|
6
|
+
|
7
|
+
通过在服务器端将网页的 HTML 内容渲染成完整的网页(Server-Side Rendering,简称 SSR),然后将生成的网页发送到浏览器端,浏览器端只需要显示网页即可,不需要再进行额外的渲染。
|
8
|
+
|
9
|
+
它主要的优势在于:
|
10
|
+
|
11
|
+
- 提高首屏加载速度:SSR 可以在服务器端生成完整的网页,浏览器端只需要下载网页内容即可,不需要再进行额外的渲染,从而提高了首屏加载速度。
|
12
|
+
- 有利于 SEO:SSR 可以生成完整的 HTML 内容,搜索引擎可以直接索引 HTML 内容,从而提高网站的排名。
|
13
|
+
|
14
|
+
如果你有以下场景的需求,开发者可以考虑使用 SSR 来渲染你的页面:
|
15
|
+
|
16
|
+
1. 对首屏加载速度要求较高的网站,如电商网站、新闻网站等。
|
17
|
+
2. 对用户体验要求较高的网站,如社交网站、游戏网站等。
|
18
|
+
3. 对 SEO 要求较高的网站,如企业官网、博客等。
|
19
|
+
|
20
|
+
在 Modern.js 中,SSR 也是开箱即用的。开发者无需为 SSR 编写复杂的服务端逻辑,也无需关心 SSR 的运维,或是创建单独的服务。
|
21
|
+
|
22
|
+
除了开箱即用的 SSR 服务,为了保证开发者的开发体验,Modern.js 还具备:
|
23
|
+
|
24
|
+
- 完备的 SSR 降级策略,保证页面能够安全运行。
|
25
|
+
- 自动分割子路由,按需加载,减少首屏资源体积。
|
26
|
+
- 内置缓存系统,解决服务端负载高的问题。
|
27
|
+
|
28
|
+
## 开启 SSR
|
29
|
+
|
30
|
+
在 Modenr.js 启用 SSR 非常简单,只需要设置 [`server.ssr`](/configure/app/server/ssr) 为 `true` 即可:
|
31
|
+
|
32
|
+
```ts title="modern.config.ts"
|
33
|
+
import { defineConfig } from '@modern-js/app-tools';
|
34
|
+
|
35
|
+
export default defineConfig({
|
36
|
+
server: {
|
37
|
+
ssr: true,
|
38
|
+
},
|
39
|
+
});
|
40
|
+
```
|
41
|
+
|
42
|
+
## 数据获取
|
43
|
+
|
44
|
+
:::tip 前置阅读
|
45
|
+
如果你还不了解 Data Loader 如何使用或 Client Loader 的概念,请先阅读[数据获取](/guides/basic-features/data/data-fetch)。
|
46
|
+
:::
|
47
|
+
|
48
|
+
### 基本用法
|
49
|
+
|
50
|
+
Modern.js 中提供了 Data Loader,方便开发者在 SSR、CSR 下同构地获取数据。每个路由模块,如 `layout.tsx` 和 `page.tsx` 都可以定义自己的 Data Loader:
|
51
|
+
|
52
|
+
```ts title="src/routes/page.data.ts"
|
53
|
+
export const loader = () => {
|
54
|
+
return {
|
55
|
+
message: 'Hello World',
|
56
|
+
};
|
57
|
+
};
|
58
|
+
```
|
59
|
+
|
60
|
+
在组件中可以通过 Hooks API 的方式获取 `loader` 函数返回的数据:
|
61
|
+
|
62
|
+
```tsx
|
63
|
+
import { useLoaderData } from '@modern-js/runtime/router';
|
64
|
+
export default () => {
|
65
|
+
const data = useLoaderData();
|
66
|
+
return <div>{data.message}</div>;
|
67
|
+
};
|
68
|
+
```
|
69
|
+
|
70
|
+
|
71
|
+
### 使用 Client Loader
|
72
|
+
|
73
|
+
:::info
|
74
|
+
该功能需要 x.36.0 以上版本,推荐使用框架最新版本。
|
75
|
+
:::
|
76
|
+
|
77
|
+
默认情况下,在 SSR 应用中,`loader` 函数只会在服务端执行。但有些场景下,开发者可能期望在浏览器端发送的请求不经过 SSR 服务,直接请求数据源,例如:
|
78
|
+
|
79
|
+
1. 在浏览器端希望减少网络消耗,直接请求数据源。
|
80
|
+
2. 应用在浏览器端有数据缓存,不希望请求 SSR 服务获取数据。
|
81
|
+
|
82
|
+
Modern.js 支持在 SSR 应用中额外添加 `.data.client` 文件,同样具名导出 `loader`。此时 SSR 应用在服务端执行 Data Loader 报错降级,或浏览器端切换路由时,会像 CSR 应用一样在浏览器端执行该 `loader` 函数,而不是再向 SSR 服务发送数据请求。
|
83
|
+
|
84
|
+
```ts title="page.data.client.ts"
|
85
|
+
import cache from 'my-cache';
|
86
|
+
|
87
|
+
export async function loader({ params }) {
|
88
|
+
if (cache.has(params.id)) {
|
89
|
+
return cache.get(params.id);
|
90
|
+
}
|
91
|
+
const res = await fetch('URL_ADDRESS?id={params.id}');
|
92
|
+
return {
|
93
|
+
message: res.message,
|
94
|
+
}
|
95
|
+
}
|
96
|
+
```
|
97
|
+
|
98
|
+
:::warning
|
99
|
+
要使用 Client Loader,必须有对应的 Server Loader,且 Server Loader 必须是 `.data` 文件约定,不能是 `.loader` 文件约定。
|
100
|
+
:::
|
101
|
+
|
102
|
+
## SSR 降级
|
103
|
+
|
104
|
+
在 Modern.js 中,如果应用在 SSR 过程中出现异常,Modern.js 会自动降级到 CSR 模式,并在 CSR 重新发起数据请求,保证页面能够正常展示。SSR 降级的原因主要分为两种:
|
105
|
+
|
106
|
+
1. Data Loader 执行报错
|
107
|
+
2. React 组件在服务端渲染报错
|
108
|
+
|
109
|
+
### Data Loader 执行报错
|
110
|
+
|
111
|
+
默认情况下,如果路由对应的 `loader` 函数执行报错,框架会在服务端直接渲染 `<ErrorBoundary>` 组件,并展示错误信息,这也是社区内大多数框架的默认行为。
|
112
|
+
|
113
|
+
Modern.js 也支持通过 [`server.ssr`](/configure/app/server/ssr) 配置项中的 `loaderFailureMode` 字段,自定义降级策略。当该字段被配置为 `clientRender` 时,会直接降级到 CSR 模式,并重新发起数据请求。
|
114
|
+
|
115
|
+
此时,如果路由中定义了 Client Loader,则会优先使用 Client Loader 发起数据请求。如果重新渲染仍然出错,再展示 `<ErrorBoundary>` 组件。
|
116
|
+
|
117
|
+
{/* Todo 补个图 */}
|
118
|
+
|
119
|
+
### 组件渲染报错
|
120
|
+
|
121
|
+
当组件渲染报错时,Modern.js 会自动降级到 CSR 模式,并重新发起数据请求。如果重新渲染仍然出错,则展示 `<ErrorBoundary>` 组件。
|
122
|
+
|
123
|
+
:::tip
|
124
|
+
组件渲染报错的行为,不会受到 `loaderFailureMode` 的影响,也不会在浏览器端执行 Client Loader。
|
125
|
+
:::
|
126
|
+
|
127
|
+
{/* Todo 补个图 */}
|
128
|
+
|
129
|
+
## 日志与监控
|
130
|
+
|
131
|
+
import Monitor from '@site-docs/components/ssr-monitor';
|
132
|
+
|
133
|
+
<Monitor />
|
134
|
+
|
135
|
+
## 页面缓存
|
136
|
+
|
137
|
+
Modern.js 中内置了缓存的能力,详细请参考[渲染缓存](/guides/basic-features/render/ssr-cache)。
|
138
|
+
|
139
|
+
## 运行环境差异
|
140
|
+
|
141
|
+
SSR 应用会同时运行在服务端和浏览器端,两者在运行环境上不完全相同,存在 Web API 和 Node API 的差异。
|
142
|
+
|
143
|
+
开启 SSR 时,Modern.js 会用相同的入口,构建出 SSR Bundle 和 CSR Bundle 两份产物。因此,在 SSR Bundle 中存在 Web API,或是在 CSR Bundle 中存在 Node API 时,都可能导致运行出错。出现这类问题的场景主要是两类:
|
144
|
+
|
145
|
+
- 应用自身代码存在问题
|
146
|
+
- 应用依赖的包中存在副作用
|
147
|
+
|
148
|
+
### 自身代码问题
|
149
|
+
|
150
|
+
这种场景通常出现在应用从 CSR 迁移到 SSR,CSR 应用通常会在代码中引入 Web API。例如应用希望做全局的事件监听:
|
151
|
+
|
152
|
+
```tsx
|
153
|
+
document.addEventListener('load', () => {
|
154
|
+
console.log('document load');
|
155
|
+
});
|
156
|
+
const App = () => {
|
157
|
+
return <div>Hello World</div>;
|
158
|
+
};
|
159
|
+
export default App;
|
160
|
+
```
|
161
|
+
|
162
|
+
对于这种场景,你可以直接使用 Modern.js 内置的环境变量 `MODERN_TARGET` 进行判断,在构建时删除无用代码:
|
163
|
+
|
164
|
+
```ts
|
165
|
+
if (process.env.MODERN_TARGET === 'browser') {
|
166
|
+
document.addEventListener('load', () => {
|
167
|
+
console.log('document load');
|
168
|
+
});
|
169
|
+
}
|
170
|
+
```
|
171
|
+
|
172
|
+
开发环境打包后,SSR 产物和 CSR 产物会被编译成以下内容。因此 SSR 环境中不会再因为 Web API 报错:
|
173
|
+
|
174
|
+
```ts
|
175
|
+
// SSR 产物
|
176
|
+
if (false) {
|
177
|
+
}
|
178
|
+
|
179
|
+
// CSR 产物
|
180
|
+
if (true) {
|
181
|
+
document.addEventListener('load', () => {
|
182
|
+
console.log('document load');
|
183
|
+
});
|
184
|
+
}
|
185
|
+
```
|
186
|
+
|
187
|
+
:::note
|
188
|
+
更多内容可以查看[环境变量](/guides/basic-features/env-vars)。
|
189
|
+
:::
|
190
|
+
|
191
|
+
### 依赖中的副作用
|
192
|
+
|
193
|
+
这类场景是在 SSR 应用中随时可能出现的,因为社区中的包并不都支持在两个运行环境中运行,有些包也无需在两个环境中运行。例如在代码中引入了包 A,它内部有使用了 Web API 的副作用:
|
194
|
+
|
195
|
+
```ts title="packageA"
|
196
|
+
document.addEventListener('load', () => {
|
197
|
+
console.log('document load');
|
198
|
+
});
|
199
|
+
|
200
|
+
export const doSomething = () => {}
|
201
|
+
```
|
202
|
+
|
203
|
+
如果直接引用到组件中,会造成 CSR 加载报错,即使你已经使用环境变量进行判断,但仍然无法移除依赖中副作用的执行。
|
204
|
+
|
205
|
+
```tsx title="routes/page.tsx"
|
206
|
+
import { doSomething } from 'packageA';
|
207
|
+
|
208
|
+
export const Page = () => {
|
209
|
+
if (process.env.MODERN_TARGET === 'browser') {
|
210
|
+
doSomething();
|
211
|
+
}
|
212
|
+
return <div>Hello World</div>
|
213
|
+
}
|
214
|
+
```
|
215
|
+
|
216
|
+
Modern.js 也支持通过 `.server.` 后缀的文件来区分 SSR Bundle 和 CSR Bundle 产物的打包文件。可以创建同名的 `.ts` 和 `.server.ts` 文件做一层代理:
|
217
|
+
|
218
|
+
```ts title="a.ts"
|
219
|
+
export { doSomething } from 'packageA';
|
220
|
+
```
|
221
|
+
|
222
|
+
```ts title="a.server.ts"
|
223
|
+
export const doSomething: any = () => {};
|
224
|
+
```
|
225
|
+
|
226
|
+
在文件中直接引入 `./a`,此时 SSR 打包下会优先使用 `.server.ts` 后缀的文件,CSR 打包下会使用 `.ts` 后缀的文件。
|
227
|
+
|
228
|
+
```tsx title="routes/page.tsx"
|
229
|
+
import { doSomething } from './a'
|
230
|
+
|
231
|
+
export const Page = () => {
|
232
|
+
doSomething();
|
233
|
+
return <div>Hello World</div>
|
234
|
+
}
|
235
|
+
```
|
236
|
+
|
237
|
+
## 常见问题
|
238
|
+
|
239
|
+
### 保持渲染一致
|
240
|
+
|
241
|
+
SSR 业务需要保证在服务端渲染时的结果和浏览器端 Hydrate 的结果一致,否则很有可能出现不符合预期的渲染结果。这里通过一个例子,演示当 SSR 与 CSR 渲染不一致时出现的问题,在组件中添加以下代码:
|
242
|
+
|
243
|
+
```tsx
|
244
|
+
{
|
245
|
+
typeof window !== 'undefined' ? <div>browser content</div> : null;
|
246
|
+
}
|
247
|
+
```
|
248
|
+
|
249
|
+
启动应用后,访问页面,会发现浏览器控制台抛出警告信息:
|
250
|
+
|
251
|
+
```sh
|
252
|
+
Warning: Expected server HTML to contain a matching <div> in <div>.
|
253
|
+
```
|
254
|
+
|
255
|
+
这是 React hydrate 结果与 SSR 渲染结果不一致造成的。虽然当前页面表现正常,但在复杂应用中,很有可能因此出现 DOM 层级混乱、样式混乱等问题。
|
256
|
+
|
257
|
+
:::info
|
258
|
+
关于 React hydrate 逻辑请参考[这里](https://zh-hans.react.dev/reference/react-dom/hydrate)。
|
259
|
+
|
260
|
+
:::
|
261
|
+
|
262
|
+
应用需要保持 SSR 与 CSR 渲染结果的一致性,如果存在不一致的情况,说明这部分内容无需在 SSR 中进行渲染。Modern.js 为这类在 SSR 中不需要渲染的内容提供 [`<NoSSR>` 工具组件](/apis/app/runtime/core/use-runtime-context):
|
263
|
+
|
264
|
+
```ts
|
265
|
+
import { NoSSR } from '@modern-js/runtime/ssr';
|
266
|
+
```
|
267
|
+
|
268
|
+
在不需要进行 SSR 的元素外部,用 `NoSSR` 组件包裹:
|
269
|
+
|
270
|
+
```tsx
|
271
|
+
<NoSSR>
|
272
|
+
<div>browser content</div>
|
273
|
+
</NoSSR>
|
274
|
+
```
|
275
|
+
|
276
|
+
修改代码后,刷新页发现之前的 Waring 消失。打开浏览器开发者工具的 Network 窗口,查看返回的 HTML 文档是不包含 `NoSSR` 组件包裹的内容的。
|
277
|
+
|
278
|
+
在实际场景中,有些应用的 UI 展示会和用户设备有关,例如 [UA](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) 信息。Modern.js 也提供了
|
279
|
+
[`useRuntimeContext`](/apis/app/runtime/core/use-runtime-context) 这类 API,可以在组件中获取完整的请求信息,利用它保证 SSR 与 CSR 的渲染结果一致。
|
280
|
+
|
281
|
+
### 关注内存泄漏
|
282
|
+
|
283
|
+
:::warning 警告
|
284
|
+
在 SSR 场景下,开发者需要特别关注内存泄露问题,即使是微小的内存泄露,在大量的访问后也会对服务造成影响。
|
285
|
+
|
286
|
+
:::
|
287
|
+
|
288
|
+
SSR 时,浏览器的每次请求,都会触发服务端重新执行一次组件渲染逻辑。所以,需要避免在全局定义任何可能不断增长的数据结构,或在全局进行事件订阅,或创建不会被销毁的流。
|
289
|
+
|
290
|
+
例如以下代码,使用 [redux-observable](https://redux-observable.js.org/) 时,习惯了 CSR 的开发者通常会在组件中这样编码:
|
291
|
+
|
292
|
+
```tsx
|
293
|
+
/* 代码仅作为示例,不可运行 */
|
294
|
+
import { createEpicMiddleware, combineEpics } from 'redux-observable';
|
295
|
+
|
296
|
+
const epicMiddleware = createEpicMiddleware();
|
297
|
+
const rootEpic = combineEpics();
|
298
|
+
|
299
|
+
export default function Test() {
|
300
|
+
epicMiddleware.run(rootEpic);
|
301
|
+
return <div>Hello Modern.js</div>;
|
302
|
+
}
|
303
|
+
```
|
304
|
+
|
305
|
+
在组件外层创建 Middleware 实例 `epicMiddleware`,并在组件内部调用 `epicMiddleware.run`。
|
306
|
+
|
307
|
+
在浏览器端,这段代码不会造成任何问题,但是在 SSR 时,Middleware 实例会一直无法被销毁。每次渲染组件,调用 `epicMiddleware.run(rootEpic)` 时,都会在内部添加新的事件绑定,导致整个对象不断变大,最终对应用性能造成影响。
|
308
|
+
|
309
|
+
CSR 中这类问题不易被发觉,因此从 CSR 切换到 SSR 时,如果不确定应用是否存在这类隐患,可以对应用进行压测。
|
package/docs/zh/guides/{advanced-features/ssr/stream.mdx → basic-features/render/streaming-ssr.mdx}
RENAMED
@@ -1,11 +1,8 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
title: 流式渲染
|
2
|
+
title: 服务端流式渲染(Streaming SSR)
|
4
3
|
---
|
5
4
|
|
6
|
-
#
|
7
|
-
|
8
|
-
## 概述
|
5
|
+
# 服务端流式渲染(Streaming SSR)
|
9
6
|
|
10
7
|
流式渲染是一种新的渲染方式,它可以在用户与页面交互时,实时地更新页面内容,从而提高用户体验。
|
11
8
|
|
@@ -18,7 +15,7 @@ title: 流式渲染
|
|
18
15
|
- 拥有更好的性能控制:流式渲染可以让开发者更好地控制页面加载的优先级和顺序,从而更好地优化性能和用户体验。
|
19
16
|
- 更好的适应性:流式渲染可以更好地适应不同网络速度和设备性能,使得页面在各种环境下都能有更好的表现。
|
20
17
|
|
21
|
-
##
|
18
|
+
## 开启流式渲染
|
22
19
|
|
23
20
|
Modern.js 支持了 React 18 的流式渲染,可以通过如下配置启用:
|
24
21
|
|
@@ -40,9 +37,9 @@ Modern.js 的流式渲染基于 React Router 实现,主要涉及 API 有:
|
|
40
37
|
- [`Await`](https://reactrouter.com/en/main/components/await):用于渲染 Data Loader 返回的异步数据。
|
41
38
|
- [`useAsyncValue`](https://reactrouter.com/en/main/hooks/use-async-value):用于从最近的父级 `Await` 组件中获取数据。
|
42
39
|
|
43
|
-
|
40
|
+
## 获取数据
|
44
41
|
|
45
|
-
```ts title="page.data.ts"
|
42
|
+
```ts title="user/[id]/page.data.ts"
|
46
43
|
import { defer, type LoaderFunctionArgs } from '@modern-js/runtime/router';
|
47
44
|
|
48
45
|
interface User {
|
@@ -70,14 +67,11 @@ export const loader = ({ params }: LoaderFunctionArgs) => {
|
|
70
67
|
};
|
71
68
|
```
|
72
69
|
|
73
|
-
`user` 是一个 Promise 类型的对象,表示需要异步获取的数据,通过 `defer` 处理需要异步获取的 `user`。注意,`defer`
|
74
|
-
因此, 传入 `defer` 的参数为:`{ data: user }`
|
75
|
-
|
76
|
-
`defer` 还可以同时接收异步数据和同步数据。例如:
|
70
|
+
`user` 是一个 Promise 类型的对象,表示需要异步获取的数据,通过 `defer` 处理需要异步获取的 `user`。注意,`defer` 必须接收一个对象类型的参数,不能直接传递 Promise 对象。
|
77
71
|
|
78
|
-
|
79
|
-
// 省略部分代码
|
72
|
+
另外,`defer` 还可以同时接收异步数据和同步数据。在下述例子中,我们等待部分耗时较短的请求,在响应后通过对象数据返回,而耗时较长时间的请求,则通过 Promise 返回:
|
80
73
|
|
74
|
+
```ts title="user/[id]/page.data.ts"
|
81
75
|
export const loader = ({ params }: LoaderFunctionArgs) => {
|
82
76
|
const userId = params.id;
|
83
77
|
|
@@ -87,7 +81,7 @@ export const loader = ({ params }: LoaderFunctionArgs) => {
|
|
87
81
|
name: `user-${userId}`,
|
88
82
|
age: 18,
|
89
83
|
});
|
90
|
-
},
|
84
|
+
}, 2000);
|
91
85
|
});
|
92
86
|
|
93
87
|
const otherData = new Promise<string>(resolve => {
|
@@ -103,13 +97,13 @@ export const loader = ({ params }: LoaderFunctionArgs) => {
|
|
103
97
|
};
|
104
98
|
```
|
105
99
|
|
106
|
-
|
100
|
+
这样,应用无需等待最耗时的数据请求响应后才展示页面内容,可以优先展示部分有数据的页面内容
|
107
101
|
|
108
|
-
|
102
|
+
## 渲染数据
|
109
103
|
|
110
104
|
通过 `Await` 组件,可以获取到 Data Loader 中异步返回的数据,然后进行渲染。例如:
|
111
105
|
|
112
|
-
```tsx title="page.tsx"
|
106
|
+
```tsx title="user/[id]/page.tsx"
|
113
107
|
import { Await, useLoaderData } from '@modern-js/runtime/router';
|
114
108
|
import { Suspense } from 'react';
|
115
109
|
import type { Data } from './page.data';
|
@@ -143,22 +137,16 @@ export default Page;
|
|
143
137
|
`Suspense` 组件 `fallback` 属性设置的内容。
|
144
138
|
|
145
139
|
:::warning 注意
|
146
|
-
从
|
147
|
-
|
148
|
-
所以,这里的导入方式为:`import type { Data } from './page.data'`;
|
149
|
-
|
140
|
+
从 `page.data.ts` 文件导入类型时,需要使用 `import type` 语法,保证只导入类型信息,避免 Data Loader 的代码打包到前端产物中。
|
150
141
|
:::
|
151
142
|
|
152
|
-
|
143
|
+
在组件中,你也可以通过 `useAsyncValue` 获取 Data Loader 返回的异步数据。例如:
|
153
144
|
|
154
145
|
```tsx title='page.tsx'
|
155
146
|
import { useAsyncValue } from '@modern-js/runtime/router';
|
156
147
|
|
157
|
-
// 省略部分代码
|
158
|
-
|
159
148
|
const UserInfo = () => {
|
160
149
|
const user = useAsyncValue();
|
161
|
-
|
162
150
|
return (
|
163
151
|
<div>
|
164
152
|
name: {user.name}, age: {user.age}
|
@@ -168,7 +156,6 @@ const UserInfo = () => {
|
|
168
156
|
|
169
157
|
const Page = () => {
|
170
158
|
const data = useLoaderData() as Data;
|
171
|
-
|
172
159
|
return (
|
173
160
|
<div>
|
174
161
|
User info:
|
@@ -184,10 +171,9 @@ const Page = () => {
|
|
184
171
|
export default Page;
|
185
172
|
```
|
186
173
|
|
187
|
-
|
174
|
+
## 错误处理
|
188
175
|
|
189
|
-
`Await` 组件的 `errorElement`
|
190
|
-
例如,我们故意在 Data Loader 函数中抛出错误:
|
176
|
+
`Await` 组件的 `errorElement` 属性,可以用来处理 Data Loader 或者子组件渲染时的报错。例如,我们故意在 Data Loader 函数中抛出错误:
|
191
177
|
|
192
178
|
```ts title="page.loader.ts"
|
193
179
|
import { defer } from '@modern-js/runtime/router';
|
@@ -238,15 +224,14 @@ function ErrorElement() {
|
|
238
224
|
|
239
225
|
然而,当一个爬虫访问该页面时,它可能需要先加载所有内容,直接输出整个 HTML,而不是渐进式地加载它。
|
240
226
|
|
241
|
-
Modern.js 使用 [isbot](https://www.npmjs.com/package/isbot) 对请求的 `uesr-agent
|
227
|
+
Modern.js 使用 [isbot](https://www.npmjs.com/package/isbot) 对请求的 `uesr-agent`,以判断请求是否来自爬虫。
|
242
228
|
|
243
|
-
:::info 补充信息
|
244
|
-
|
245
|
-
1. [Deferred Data](https://reactrouter.com/en/main/guides/deferred)
|
246
|
-
2. [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37)
|
247
|
-
|
248
|
-
:::
|
249
229
|
|
250
230
|
import StreamSSRPerformance from '@site-docs/components/stream-ssr-performance';
|
251
231
|
|
252
232
|
<StreamSSRPerformance />
|
233
|
+
|
234
|
+
## 相关文档
|
235
|
+
|
236
|
+
1. [Deferred Data](https://reactrouter.com/en/main/guides/deferred)
|
237
|
+
2. [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37)
|