@modern-js/main-doc 2.42.2 → 2.44.0
Sign up to get free protection for your applications and to get access to all the features.
- package/docs/en/apis/app/runtime/web-server/middleware.mdx +3 -3
- package/docs/en/configure/app/server/ssr.mdx +2 -0
- package/docs/en/guides/advanced-features/optimize-bundle.mdx +1 -1
- package/docs/en/guides/advanced-features/ssr/_category_.json +8 -0
- package/docs/en/guides/advanced-features/ssr/cache.mdx +186 -0
- package/docs/en/guides/advanced-features/ssr/index.mdx +22 -0
- package/docs/en/guides/advanced-features/ssr/stream.mdx +236 -0
- package/docs/en/guides/advanced-features/ssr/usage.mdx +341 -0
- package/docs/en/guides/basic-features/css.mdx +2 -13
- package/docs/en/guides/get-started/tech-stack.mdx +1 -1
- package/docs/en/guides/topic-detail/framework-plugin/introduction.mdx +63 -16
- package/docs/zh/apis/app/runtime/web-server/middleware.mdx +4 -4
- package/docs/zh/configure/app/server/ssr.mdx +2 -0
- package/docs/zh/guides/advanced-features/optimize-bundle.mdx +1 -1
- package/docs/zh/guides/advanced-features/ssr/_category_.json +8 -0
- package/docs/zh/guides/advanced-features/ssr/cache.mdx +189 -0
- package/docs/zh/guides/advanced-features/ssr/index.mdx +22 -0
- package/docs/zh/guides/advanced-features/ssr/stream.mdx +240 -0
- package/docs/zh/guides/advanced-features/{ssr.mdx → ssr/usage.mdx} +7 -225
- package/docs/zh/guides/basic-features/css.mdx +2 -13
- package/docs/zh/guides/basic-features/data/data-write.mdx +1 -1
- package/docs/zh/guides/get-started/tech-stack.mdx +1 -1
- package/docs/zh/guides/topic-detail/framework-plugin/introduction.mdx +61 -16
- package/package.json +7 -7
@@ -91,7 +91,7 @@ The execution of the `next` function does not affect built-in processes, only co
|
|
91
91
|
```ts
|
92
92
|
export const Middleware = () => async (ctx, next) => {
|
93
93
|
const start = Date.now();
|
94
|
-
ctx.res.once('finish', () => {
|
94
|
+
ctx.source.res.once('finish', () => {
|
95
95
|
console.log(Date.now() - start);
|
96
96
|
});
|
97
97
|
};
|
@@ -103,8 +103,8 @@ Modern.js provides `res.locals` to store local variables for the current request
|
|
103
103
|
|
104
104
|
```ts
|
105
105
|
export const Middleware = () => async (ctx, next) => {
|
106
|
-
ctx.
|
107
|
-
ctx.
|
106
|
+
ctx.response.locals.id = 'Modern.js';
|
107
|
+
ctx.response.locals.rpc = createRpcInstance();
|
108
108
|
};
|
109
109
|
```
|
110
110
|
|
@@ -30,6 +30,7 @@ When the value type is `Object`, the following properties can be configured:
|
|
30
30
|
- `inlineScript`: `boolean = true`, by default, SSR data is injected into HTML as inline scripts and assigned directly to global variables. Configure `false` to distribute JSON instead of assigning to global variables.
|
31
31
|
- `disablePrerender`: `boolean = fasle`, To ensure compatibility with the old data request method (`useLoader`), by default, Modern.js performs pre-rendering of components.
|
32
32
|
However, if developers want to reduce one rendering when there is no use of the useLoader API in your project, you can set the configuration `disablePrerender=true`.
|
33
|
+
- `unsafeHeaders`: `string[] = []`, For safety reasons, Modern.js does not add excessive content to SSR_DATA. Developers can use this configuration to specify the headers that need to be injected.
|
33
34
|
|
34
35
|
```ts title="modern.config.ts"
|
35
36
|
export default defineConfig({
|
@@ -38,6 +39,7 @@ export default defineConfig({
|
|
38
39
|
forceCSR: true,
|
39
40
|
mode: 'stream',
|
40
41
|
inlineScript: false,
|
42
|
+
unsafeHeaders: ['User-Agent'],
|
41
43
|
},
|
42
44
|
},
|
43
45
|
});
|
@@ -77,7 +77,7 @@ export default {
|
|
77
77
|
```
|
78
78
|
|
79
79
|
:::tip
|
80
|
-
Please read the [Browser Compatibility](
|
80
|
+
Please read the [Browser Compatibility](/guides/advanced-features/compatibility) chapter to know more about the usage of Browserslist.
|
81
81
|
:::
|
82
82
|
|
83
83
|
## Image Compression
|
@@ -0,0 +1,186 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 3
|
3
|
+
title: Cache
|
4
|
+
---
|
5
|
+
|
6
|
+
# Render Cache
|
7
|
+
|
8
|
+
Sometimes we cache computation results, such as with the React useMemo, useCallback Hook. By caching, we can reduce the number of computations, thus reducing CPU resource usage and enhancing the user experience.
|
9
|
+
|
10
|
+
Caching the results of server-side rendering (SSR) can reduce the computation and rendering time for each server request, enabling faster page load speeds and improving the user experience. It also lowers the server load, saves computational resources, and speeds up user access.
|
11
|
+
|
12
|
+
:::info
|
13
|
+
Need x.43.0+
|
14
|
+
:::
|
15
|
+
|
16
|
+
## Configuration
|
17
|
+
|
18
|
+
You can enable caching by configuring it in `server/cache.[t|j]s`:
|
19
|
+
|
20
|
+
```ts title="server/cache.ts"
|
21
|
+
import type { CacheOption } from '@modern-js/runtime/server';
|
22
|
+
|
23
|
+
export const cacheOption: CacheOption = {
|
24
|
+
maxAge: 500, // ms
|
25
|
+
staleWhileRevalidate: 1000, // ms
|
26
|
+
};
|
27
|
+
```
|
28
|
+
|
29
|
+
## Configuration Explanation
|
30
|
+
|
31
|
+
### Cache Configuration
|
32
|
+
|
33
|
+
The caching policy is implemented based on [stale-while-revalidate](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
|
34
|
+
|
35
|
+
During the `maxAge` period, cache content will be returned directly. After `maxAge` but within `staleWhileRevalidate`, the cache content will also be returned directly, but at the same time, re-rendering will be executed asynchronously.
|
36
|
+
|
37
|
+
**Object type**
|
38
|
+
|
39
|
+
```ts
|
40
|
+
export interface CacheControl {
|
41
|
+
maxAge: number;
|
42
|
+
|
43
|
+
staleWhileRevalidate: number;
|
44
|
+
|
45
|
+
customKey?: string | ((pathname: string) => string);
|
46
|
+
}
|
47
|
+
```
|
48
|
+
|
49
|
+
In this, customKey is used for custom cache key. By default, Modern.js will use the request pathname as key for caching, but in some cases, this may not meet your needs, so developers can customise it.
|
50
|
+
|
51
|
+
**Function type**
|
52
|
+
|
53
|
+
```ts
|
54
|
+
export type CacheOptionProvider = (
|
55
|
+
req: IncomingMessage,
|
56
|
+
) => Promise<CacheControl> | CacheControl;
|
57
|
+
```
|
58
|
+
|
59
|
+
Sometimes developers need to customise the cache key through req, which can be handled using the function form, as shown in the following code:
|
60
|
+
|
61
|
+
```ts title="server/cache.ts"
|
62
|
+
import type { CacheOption, CacheOptionProvider } from '@modern-js/runtime/server';
|
63
|
+
|
64
|
+
const provider: CacheOptionProvider = (req) => {
|
65
|
+
const { url, headers, ... } = req;
|
66
|
+
|
67
|
+
const key = computedKey(url, headers, ...);
|
68
|
+
|
69
|
+
return {
|
70
|
+
maxAge: 500, // ms
|
71
|
+
staleWhileRevalidate: 1000, // ms
|
72
|
+
customKey: key,
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
export const cacheOption: CacheOption = provider;
|
77
|
+
```
|
78
|
+
|
79
|
+
**Mapping type**
|
80
|
+
|
81
|
+
```ts
|
82
|
+
export type CacheOptions = Record<string, CacheControl | CacheOptionProvider>;
|
83
|
+
```
|
84
|
+
|
85
|
+
Sometimes, developers need to apply different caching policies for different routes. We also provide a mapping way for configuration, as shown in example below:
|
86
|
+
|
87
|
+
```ts title="server/cache.ts"
|
88
|
+
import type { CacheOption } from '@modern-js/runtime/server';
|
89
|
+
|
90
|
+
export const cacheOption: CacheOption = {
|
91
|
+
'/home': {
|
92
|
+
maxAge: 50,
|
93
|
+
staleWhileRevalidate: 100,
|
94
|
+
},
|
95
|
+
'/about': {
|
96
|
+
maxAge: 1000 * 60 * 60 * 24, // one day
|
97
|
+
staleWhileRevalidate: 1000 * 60 * 60 * 24 * 2 // two day
|
98
|
+
},
|
99
|
+
'*': (req) => { // If the above routes cannot be matched, it will match to '*'
|
100
|
+
const { url, headers, ... } = req;
|
101
|
+
const key = computedKey(url, headers, ...);
|
102
|
+
|
103
|
+
return {
|
104
|
+
maxAge: 500,
|
105
|
+
staleWhileRevalidate: 1000,
|
106
|
+
customKey: key,
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
```
|
111
|
+
|
112
|
+
- The route `http://xxx/home` will apply the first rule.
|
113
|
+
- The route `http://xxx/about` will apply the second rule.
|
114
|
+
- The route `http://xxx/abc` will apply the last rule.
|
115
|
+
|
116
|
+
The above-mentioned `/home` and `/about` will be used as patterns for matching, which means that `/home/abc` will also comply with this rule.
|
117
|
+
Simultaneously, you can also include regular expression syntax in it, such as `/home/.+`.
|
118
|
+
|
119
|
+
### Cache Container
|
120
|
+
|
121
|
+
By default, Server will use memory for caching. But typically, services will be deployed on serverless. Each service access may be a new process, so caching cannot be applied every time.
|
122
|
+
|
123
|
+
Therefore, developers can also customise the cache container, which needs to implement the `Container` interface.
|
124
|
+
|
125
|
+
```ts
|
126
|
+
export interface Container<K = string, V = string> {
|
127
|
+
/**
|
128
|
+
* Returns a specified element from the container. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Container.
|
129
|
+
* @returns Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
|
130
|
+
*/
|
131
|
+
get: (key: K) => Promise<V | undefined>;
|
132
|
+
|
133
|
+
/**
|
134
|
+
* Adds a new element with a specified key and value to the container. If an element with the same key already exists, the element will be updated.
|
135
|
+
*
|
136
|
+
* The ttl indicates cache expiration time.
|
137
|
+
*/
|
138
|
+
set: (key: K, value: V, options?: { ttl?: number }) => Promise<this>;
|
139
|
+
|
140
|
+
/**
|
141
|
+
* @returns boolean indicating whether an element with the specified key exists or not.
|
142
|
+
*/
|
143
|
+
has: (key: K) => Promise<boolean>;
|
144
|
+
|
145
|
+
/**
|
146
|
+
* @returns true if an element in the container existed and has been removed, or false if the element does not exist.
|
147
|
+
*/
|
148
|
+
delete: (key: K) => Promise<boolean>;
|
149
|
+
}
|
150
|
+
```
|
151
|
+
|
152
|
+
As an example in the following code, a developer can implement a Redis container.
|
153
|
+
|
154
|
+
```ts
|
155
|
+
import type { Container, CacheOption } from '@modern-js/runtime/server';
|
156
|
+
|
157
|
+
class RedisContainer implements Container {
|
158
|
+
redis = new Redis();
|
159
|
+
|
160
|
+
async get(key: string) {
|
161
|
+
return this.redis.get(key);
|
162
|
+
}
|
163
|
+
|
164
|
+
async set(key: string, value: string): Promise<this> {
|
165
|
+
this.redis.set(key, value);
|
166
|
+
return this;
|
167
|
+
}
|
168
|
+
|
169
|
+
async has(key: string): Promise<boolean> {
|
170
|
+
return this.redis.has(key);
|
171
|
+
}
|
172
|
+
|
173
|
+
async delete(key: string): Promise<boolean> {
|
174
|
+
return this.redis.delete(key);
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
const container = new RedisContainer();
|
179
|
+
|
180
|
+
export const customContainer: Container = container;
|
181
|
+
|
182
|
+
export const cacheOption: CacheOption = {
|
183
|
+
maxAge: 500, // ms
|
184
|
+
staleWhileRevalidate: 1000, // ms
|
185
|
+
};
|
186
|
+
```
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# SSR
|
2
|
+
|
3
|
+
By rendering the HTML content of web pages into complete web pages on the server side, and then sending the generated web pages to the client, the client only needs to display the web pages without further rendering.
|
4
|
+
|
5
|
+
Its main advantages are:
|
6
|
+
|
7
|
+
- Improve first screen load speed: SSR can generate complete websites on the server side, the client only needs to download the content of the website, no additional renderings are required, thus improving the first screen load speed.
|
8
|
+
- Improve user experience: SSR can improve the responsiveness of web pages, thereby enhancing the user experience.
|
9
|
+
- Good for SEO: SSR can generate complete HTML content. Search engines can directly index HTML content to improve website ranking.
|
10
|
+
|
11
|
+
If you have the following scenarios, developers can consider using SSR to render your pages:
|
12
|
+
|
13
|
+
1. Websites with higher first-screen loading speed requirements, such as e-commerce websites, news websites, etc.
|
14
|
+
2. Websites with higher user experience requirements, such as social networking sites, gaming sites, etc.
|
15
|
+
3. Websites with higher SEO requirements, such as corporate websites, blogs, etc.
|
16
|
+
|
17
|
+
In Modern.js, SSR is also out-of-the-box. Developers do not need to write complex server-side logic for SSR, nor do they need to worry about the operation and maintenance of SSR, or create separate services.
|
18
|
+
In addition to the out-of-the-box SSR service, to ensure the developer's development experience, we also have:
|
19
|
+
|
20
|
+
- A complete SSR downgrade strategy to ensure that the page can run safely.
|
21
|
+
- Automatically split sub-routes to load on demand, reducing the size of the first screen resources.
|
22
|
+
- Built-in caching system to solve the problem of high server-side load.
|
@@ -0,0 +1,236 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 2
|
3
|
+
title: Streaming SSR
|
4
|
+
---
|
5
|
+
|
6
|
+
# Streaming SSR
|
7
|
+
|
8
|
+
## Overview
|
9
|
+
|
10
|
+
Stream rendering is a new way of rendering, which can update the page content in real time when the user interacts with the page, thereby improving the user experience.
|
11
|
+
|
12
|
+
In traditional rendering, the rendering of the page is completed at once, while in stream rendering, the rendering of the page is gradually completed. When the user interacts with the page, data is loaded gradually instead of loading all at once.
|
13
|
+
|
14
|
+
Compared to traditional rendering:
|
15
|
+
|
16
|
+
- Faster perceived speed: Stream rendering can gradually display content during the rendering process to display the business home page at the fastest speed.
|
17
|
+
- Better user experience: Through stream rendering, users can see the content on the page faster, instead of waiting for the entire page to be rendered before they can interact.
|
18
|
+
- Better performance control: Stream rendering allows developers to better control the loading priority and order of pages, thereby optimizing performance and user experience.
|
19
|
+
- Better adaptability: Stream rendering can better adapt to different network speeds and device performances, allowing the page to perform better in various environments.
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Modern.js supports streaming rendering in React 18 which can be enabled through the following configuration:
|
24
|
+
|
25
|
+
```ts title="modern.config.ts"
|
26
|
+
import { defineConfig } from '@modern-js/app-tools';
|
27
|
+
|
28
|
+
export default defineConfig({
|
29
|
+
server: {
|
30
|
+
ssr: {
|
31
|
+
mode: 'stream',
|
32
|
+
},
|
33
|
+
},
|
34
|
+
});
|
35
|
+
```
|
36
|
+
|
37
|
+
The streaming SSR of Modern.js is implemented based on React Router, and the main APIs involved are:
|
38
|
+
|
39
|
+
- [`defer`](https://reactrouter.com/en/main/utils/defer): This utility allows you to defer values returned from loaders by passing promises instead of resolved values.
|
40
|
+
- [`Await`](https://reactrouter.com/en/main/components/await): Used to render deferred values with automatic error handling.
|
41
|
+
- [`useAsyncValue`](https://reactrouter.com/en/main/hooks/use-async-value): Returns the resolved data from the nearest `<Await>` ancestor component.
|
42
|
+
|
43
|
+
### Return async data
|
44
|
+
|
45
|
+
```ts title="page.data.ts"
|
46
|
+
import { defer, type LoaderFunctionArgs } from '@modern-js/runtime/router';
|
47
|
+
|
48
|
+
interface User {
|
49
|
+
name: string;
|
50
|
+
age: number;
|
51
|
+
}
|
52
|
+
|
53
|
+
export interface Data {
|
54
|
+
data: User;
|
55
|
+
}
|
56
|
+
|
57
|
+
export const loader = ({ params }: LoaderFunctionArgs) => {
|
58
|
+
const userId = params.id;
|
59
|
+
|
60
|
+
const user = new Promise<User>(resolve => {
|
61
|
+
setTimeout(() => {
|
62
|
+
resolve({
|
63
|
+
name: `user-${userId}`,
|
64
|
+
age: 18,
|
65
|
+
});
|
66
|
+
}, 200);
|
67
|
+
});
|
68
|
+
|
69
|
+
return defer({ data: user });
|
70
|
+
};
|
71
|
+
```
|
72
|
+
|
73
|
+
`user` is a `Promise` object that represents the data that needs to be obtained asynchronously. Use `defer` to handle the asynchronous retrieval of user. Note that `defer` must receive an object type parameter, so the parameter passed to `defer` is: `{ data: user }`.
|
74
|
+
|
75
|
+
`defer` can receive both asynchronous and synchronous data at the same time. For example:
|
76
|
+
|
77
|
+
```ts title="page.data.ts"
|
78
|
+
// skip some codes
|
79
|
+
|
80
|
+
export default ({ params }: LoaderFunctionArgs) => {
|
81
|
+
const userId = params.id;
|
82
|
+
|
83
|
+
const user = new Promise<User>(resolve => {
|
84
|
+
setTimeout(() => {
|
85
|
+
resolve({
|
86
|
+
name: `user-${userId}`,
|
87
|
+
age: 18,
|
88
|
+
});
|
89
|
+
}, 200);
|
90
|
+
});
|
91
|
+
|
92
|
+
const otherData = new Promise<string>(resolve => {
|
93
|
+
setTimeout(() => {
|
94
|
+
resolve('some sync data');
|
95
|
+
}, 200);
|
96
|
+
});
|
97
|
+
|
98
|
+
return defer({
|
99
|
+
data: user,
|
100
|
+
other: await otherData,
|
101
|
+
});
|
102
|
+
};
|
103
|
+
```
|
104
|
+
|
105
|
+
The data obtained from otherData is synchronous because it has an `await` keyword in front of it. It can be passed into `defer` together with the asynchronous data obtained from `user`.
|
106
|
+
|
107
|
+
### Render asynchronous data.
|
108
|
+
|
109
|
+
With the `<Await>` component, you can retrieve the data asynchronously returned by the Data Loader and then render it. For example:
|
110
|
+
|
111
|
+
```tsx title="page.tsx"
|
112
|
+
import { Await, useLoaderData } from '@modern-js/runtime/router';
|
113
|
+
import { Suspense } from 'react';
|
114
|
+
import type { Data } from './page.data';
|
115
|
+
|
116
|
+
const Page = () => {
|
117
|
+
const data = useLoaderData() as Data;
|
118
|
+
|
119
|
+
return (
|
120
|
+
<div>
|
121
|
+
User info:
|
122
|
+
<Suspense fallback={<div id="loading">loading user data ...</div>}>
|
123
|
+
<Await resolve={data.data}>
|
124
|
+
{user => {
|
125
|
+
return (
|
126
|
+
<div id="data">
|
127
|
+
name: {user.name}, age: {user.age}
|
128
|
+
</div>
|
129
|
+
);
|
130
|
+
}}
|
131
|
+
</Await>
|
132
|
+
</Suspense>
|
133
|
+
</div>
|
134
|
+
);
|
135
|
+
};
|
136
|
+
|
137
|
+
export default Page;
|
138
|
+
```
|
139
|
+
|
140
|
+
`<Await>` needs to be wrapped inside the `<Suspense>` component. The `resolve` function passed into `<Await>` is used to asynchronously retrieve data from a Data Loader. Once the data has been retrieved, it is rendered using [Render Props](https://reactjs.org/docs/render-props.html) mode. During the data retrieval phase, the content specified in the `fallback` property of `<Suspense>` will be displayed.
|
141
|
+
|
142
|
+
:::warning Warning
|
143
|
+
When importing types from a Data Loader file, it is necessary to use the `import type` syntax to ensure that only type information is imported. This can avoid packaging Data Loader code into the bundle file of the front-end product.
|
144
|
+
|
145
|
+
Therefore, the import method here is: `import type { Data } from './page.data'`;
|
146
|
+
:::
|
147
|
+
|
148
|
+
You can also retrieve asynchronous data returned by the Data Loader using `useAsyncValue`. For example:
|
149
|
+
|
150
|
+
```tsx title="page.tsx"
|
151
|
+
import { useAsyncValue } from '@modern-js/runtime/router';
|
152
|
+
|
153
|
+
// skip some codes
|
154
|
+
|
155
|
+
const UserInfo = () => {
|
156
|
+
const user = useAsyncValue();
|
157
|
+
|
158
|
+
return (
|
159
|
+
<div>
|
160
|
+
name: {user.name}, age: {user.age}
|
161
|
+
</div>
|
162
|
+
);
|
163
|
+
};
|
164
|
+
|
165
|
+
const Page = () => {
|
166
|
+
const data = useLoaderData() as Data;
|
167
|
+
|
168
|
+
return (
|
169
|
+
<div>
|
170
|
+
User info:
|
171
|
+
<Suspense fallback={<div id="loading">loading user data ...</div>}>
|
172
|
+
<Await resolve={data.data}>
|
173
|
+
<UserInfo />
|
174
|
+
</Await>
|
175
|
+
</Suspense>
|
176
|
+
</div>
|
177
|
+
);
|
178
|
+
};
|
179
|
+
|
180
|
+
export default Page;
|
181
|
+
```
|
182
|
+
|
183
|
+
### Error handling
|
184
|
+
|
185
|
+
The `errorElement` property of the `<Await>` component can be used to handle errors thrown when the Data Loader executes or when a child component renders.
|
186
|
+
For example, we intentionally throw an error in the Data Loader function:
|
187
|
+
|
188
|
+
```ts title="page.data.ts"
|
189
|
+
import { defer } from '@modern-js/runtime/router';
|
190
|
+
|
191
|
+
export const loader = () => {
|
192
|
+
const data = new Promise((resolve, reject) => {
|
193
|
+
setTimeout(() => {
|
194
|
+
reject(new Error('error occurs'));
|
195
|
+
}, 200);
|
196
|
+
});
|
197
|
+
|
198
|
+
return defer({ data });
|
199
|
+
};
|
200
|
+
```
|
201
|
+
|
202
|
+
Then use `useAsyncError` to get the error, and assign the component used to render the error to the `errorElement` property of the `<Await>` component:
|
203
|
+
|
204
|
+
```tsx title="page.ts"
|
205
|
+
import { Await, useAsyncError, useLoaderData } from '@modern-js/runtime/router';
|
206
|
+
import { Suspense } from 'react';
|
207
|
+
|
208
|
+
export default function Page() {
|
209
|
+
const data = useLoaderData();
|
210
|
+
|
211
|
+
return (
|
212
|
+
<div>
|
213
|
+
Error page
|
214
|
+
<Suspense fallback={<div>loading ...</div>}>
|
215
|
+
<Await resolve={data.data} errorElement={<ErrorElement />}>
|
216
|
+
{(data: any) => {
|
217
|
+
return <div>never displayed</div>;
|
218
|
+
}}
|
219
|
+
</Await>
|
220
|
+
</Suspense>
|
221
|
+
</div>
|
222
|
+
);
|
223
|
+
}
|
224
|
+
|
225
|
+
function ErrorElement() {
|
226
|
+
const error = useAsyncError() as Error;
|
227
|
+
return <p>Something went wrong! {error.message}</p>;
|
228
|
+
}
|
229
|
+
```
|
230
|
+
|
231
|
+
:::info More
|
232
|
+
|
233
|
+
1. [Deferred Data](https://reactrouter.com/en/main/guides/deferred)
|
234
|
+
2. [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37)
|
235
|
+
|
236
|
+
:::
|