@modern-js/main-doc 0.0.0-nightly-20231217170625 → 0.0.0-nightly-20231219170633
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/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/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/package.json +7 -7
@@ -0,0 +1,341 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 1
|
3
|
+
title: Usage
|
4
|
+
---
|
5
|
+
|
6
|
+
# Usage
|
7
|
+
|
8
|
+
Enabling SSR is simple. Just set the value of [`server.ssr`](/configure/app/server/ssr) to `true`.
|
9
|
+
|
10
|
+
```ts title="modern.config.ts"
|
11
|
+
import { defineConfig } from '@modern-js/app-tools';
|
12
|
+
|
13
|
+
export default defineConfig({
|
14
|
+
server: {
|
15
|
+
ssr: true,
|
16
|
+
},
|
17
|
+
});
|
18
|
+
```
|
19
|
+
|
20
|
+
## SSR Data Fetch
|
21
|
+
|
22
|
+
Modern.js provides a Data Loader that simplifies data fetching for developers working with SSR and CSR. Each routing module, such as `layout.tsx` and `page.tsx`, can define its own Data Loader:
|
23
|
+
|
24
|
+
```ts title="src/routes/page.data.ts"
|
25
|
+
export const loader = () => {
|
26
|
+
return {
|
27
|
+
message: 'Hello World',
|
28
|
+
};
|
29
|
+
};
|
30
|
+
```
|
31
|
+
|
32
|
+
Within the component, data returned by the `loader` function can be accessed through the Hooks API:
|
33
|
+
|
34
|
+
```tsx
|
35
|
+
export default () => {
|
36
|
+
const data = useLoaderData();
|
37
|
+
return <div>{data.message}</div>;
|
38
|
+
};
|
39
|
+
```
|
40
|
+
|
41
|
+
Modern.js breaks the traditional model of server-side rendering (SSR) development and offers users a more user-friendly SSR development experience.
|
42
|
+
|
43
|
+
This feature offers elegant degradation processing. If the SSR request fails, it will automatically downgrade and restart the request on the browser side.
|
44
|
+
|
45
|
+
Developers should still be mindful of data fallback, including `null` values or unexpected data returns. This will help prevent React rendering errors and messy results during SSR.
|
46
|
+
|
47
|
+
:::info
|
48
|
+
|
49
|
+
1. When requesting a page through client-side routing, Modern.js sends an HTTP request. The server receives the request and executes the corresponding Data Loader function for the page, then returns the execution result as a response to the browser.
|
50
|
+
|
51
|
+
2. When using Data Loader, data is fetched before rendering. Modern.js also supports obtaining data during component rendering. For more related content, please refer to [Data Fetch](/guides/basic-features/data/data-fetch).
|
52
|
+
|
53
|
+
:::
|
54
|
+
|
55
|
+
## Keep Rendering Consistent
|
56
|
+
|
57
|
+
In some businesses, it is usually necessary to make different UI displays based on the current operating container environment characteristics, such as [UA](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) information.
|
58
|
+
|
59
|
+
If not handled carefully, unexpected rendering results are likely to occur.
|
60
|
+
|
61
|
+
Here is an example to show the problem when SSR and CSR rendering are inconsistent, add the following code to the component:
|
62
|
+
|
63
|
+
```tsx
|
64
|
+
{
|
65
|
+
typeof window !== 'undefined' ? <div>browser content</div> : null;
|
66
|
+
}
|
67
|
+
```
|
68
|
+
|
69
|
+
After launching the application and accessing the page, you will find that the browser console throws a warning message.
|
70
|
+
|
71
|
+
```sh
|
72
|
+
Warning: Expected server HTML to contain a matching <div> in <div>.
|
73
|
+
```
|
74
|
+
|
75
|
+
This is caused by React's hydration logic on the client side detecting inconsistencies between the rendered result and the SSR rendering result. Although the page appears normal, in complex applications, it is likely to cause problems such as DOM hierarchy confusion and style disorder.
|
76
|
+
|
77
|
+
:::info
|
78
|
+
More information about [`React hydrate`](https://reactjs.org/docs/react-dom.html#hydrate).
|
79
|
+
|
80
|
+
:::
|
81
|
+
|
82
|
+
The application needs to maintain consistency between SSR and CSR rendering results. If there is inconsistency, it means that this part of the content does not need to be rendered in SSR.
|
83
|
+
|
84
|
+
Modern.js provides a [`<NoSSR>`](/apis/app/runtime/core/use-runtime-context) component for such content that does not need to be rendered in SSR.
|
85
|
+
|
86
|
+
```ts
|
87
|
+
import { NoSSR } from '@modern-js/runtime/ssr';
|
88
|
+
```
|
89
|
+
|
90
|
+
Wrap the element that does not require SSR with the `NoSSR` component:
|
91
|
+
|
92
|
+
```tsx
|
93
|
+
<NoSSR>
|
94
|
+
<div>client content</div>
|
95
|
+
</NoSSR>
|
96
|
+
```
|
97
|
+
|
98
|
+
After modifying the code, refreshing the page shows that the previous warning has disappeared. Opening the Network tab of the browser devtools and checking the returned HTML document does not contain content wrapped by `NoSSR` components.
|
99
|
+
|
100
|
+
:::info
|
101
|
+
['useRuntimeContext'](/apis/app/runtime/core/use-runtime-context) can get complete request information, which can be used to ensure consistent rendering results between SSR and CSR.
|
102
|
+
|
103
|
+
:::
|
104
|
+
|
105
|
+
## Pay Attention to Memory Leaks
|
106
|
+
|
107
|
+
:::warning
|
108
|
+
Developers need to pay special attention to memory leaks in the SSR mode. Even tiny memory leaks can have an impact on the service after a large number of accesses.
|
109
|
+
|
110
|
+
:::
|
111
|
+
|
112
|
+
When using SSR, each request from the browser will trigger the server to re-execute the component rendering logic. Therefore, it is necessary to avoid defining any data structures that may continue to grow globally, subscribing to events globally, or creating streams that will not be destroyed globally.
|
113
|
+
|
114
|
+
For example, when using [redux-observable](https://redux-observable.js.org/), developers who are used to CSR development usually code in the component like this:
|
115
|
+
|
116
|
+
```tsx
|
117
|
+
/* This code is for demonstration purposes only */
|
118
|
+
import { createEpicMiddleware, combineEpics } from 'redux-observable';
|
119
|
+
|
120
|
+
const epicMiddleware = createEpicMiddleware();
|
121
|
+
const rootEpic = combineEpics();
|
122
|
+
|
123
|
+
export default function Test() {
|
124
|
+
epicMiddleware.run(rootEpic);
|
125
|
+
return <div>Hello Modern.js</div>;
|
126
|
+
}
|
127
|
+
```
|
128
|
+
|
129
|
+
Create a Middleware instance `epicMiddleware` outside the component and call epicMiddleware.run inside the component.
|
130
|
+
|
131
|
+
When running on the client-side, this code will not cause any issues. However, during SSR, the Middleware instance cannot be destroyed.
|
132
|
+
|
133
|
+
Every time a component is rendered and `epicMiddleware.run(rootEpic)` is called, new event bindings are added internally which causes the entire object to grow continuously and ultimately affects application performance.
|
134
|
+
|
135
|
+
CSR issues are not easy to detect, so when switching from CSR to SSR, if you are unsure whether the application has such problems, you can perform stress testing on applications.
|
136
|
+
|
137
|
+
## Converging Server Data.
|
138
|
+
|
139
|
+
In order to maintain the data requested during the SSR phase, it can be directly used on the browser side. Modern.js will inject the data and state collected during rendering into HTML.
|
140
|
+
|
141
|
+
However, in CSR applications, there are often situations where interface data is large and component states are not converged. If SSR is used directly in this case, the rendered HTML may have a problem of being too large.
|
142
|
+
|
143
|
+
At this time, SSR may not only fail to improve user experience for applications but may also have an opposite effect.
|
144
|
+
|
145
|
+
Therefore, when using SSR, **developers need to properly slim down the application**.
|
146
|
+
|
147
|
+
1. Focus on the first screen, SSR can request only the data needed for the first screen and render the remaining parts on the browser side.
|
148
|
+
2. Remove data unrelated to rendering from the returned data of the interface.
|
149
|
+
|
150
|
+
## Serverless Pre-render
|
151
|
+
|
152
|
+
:::warning
|
153
|
+
x.43.0+ has been deprecated, Please instance of [SSR Cache](guides/advanced-features/ssr/cache).
|
154
|
+
:::
|
155
|
+
|
156
|
+
Modern.js provides the Serverless Pre-rendering (SPR) feature to improve SSR performance.
|
157
|
+
|
158
|
+
SPR uses pre-rendering and caching technology to provide responsive performance for SSR pages. It enables SSR applications to have the response speed and stability of static web pages while also maintaining dynamic data updates.
|
159
|
+
|
160
|
+
Using SPR in Modern.js is very simple. Just add the PreRender component to your component, and the page where it is located will automatically enable SPR.
|
161
|
+
|
162
|
+
Here is a simulated component that uses the useLoaderData API. The request in the Data Loader takes 2 seconds to consume.
|
163
|
+
|
164
|
+
```tsx title="page.data.ts"
|
165
|
+
import { useLoaderData } from '@modern-js/runtime/router';
|
166
|
+
|
167
|
+
export const loader = async () => {
|
168
|
+
await new Promise((resolve, reject) => {
|
169
|
+
setTimeout(() => {
|
170
|
+
resolve(null);
|
171
|
+
}, 2000);
|
172
|
+
});
|
173
|
+
|
174
|
+
return {
|
175
|
+
message: 'Hello Modern.js',
|
176
|
+
};
|
177
|
+
};
|
178
|
+
```
|
179
|
+
|
180
|
+
```tsx title="page.tsx"
|
181
|
+
import { useLoaderData } from '@modern-js/runtime/router';
|
182
|
+
|
183
|
+
export default () => {
|
184
|
+
const data = useLoaderData();
|
185
|
+
return <div>{data?.message}</div>;
|
186
|
+
};
|
187
|
+
```
|
188
|
+
|
189
|
+
After executing the `dev` command and opening the page, it is obvious that the page needs to wait 2s before returning.
|
190
|
+
|
191
|
+
Use the `<PreRender>` component for optimization next, which can be directly exported from `@modern-js/runtime/ssr`.
|
192
|
+
|
193
|
+
```ts
|
194
|
+
import { PreRender } from '@modern-js/runtime/ssr';
|
195
|
+
```
|
196
|
+
|
197
|
+
Use the `<PreRender>` component within the routing component and set the parameter `interval` to indicate that the expiration time of this rendering result is 5 seconds:
|
198
|
+
|
199
|
+
```tsx
|
200
|
+
<PreRender interval={5} />
|
201
|
+
```
|
202
|
+
|
203
|
+
After modification, execute `pnpm run build && pnpm run serve` to start the application and open the page.
|
204
|
+
|
205
|
+
The first time it is opened, there is no difference in rendering compared to before, and there is still a 2-second delay.
|
206
|
+
|
207
|
+
Clicking refresh opens the page instantly, but at this point, the page data has not changed due to the refresh because the cache has not yet expired.
|
208
|
+
|
209
|
+
After waiting for 5 seconds and refreshing the page, the data on the page remained unchanged. After refreshing again, the data changed, but the response was nearly immediate.
|
210
|
+
|
211
|
+
This is because during the previous request, SPR had already asynchronously obtained a new rendering result in the background, and this time's requested page is a version that has been cached on the server.
|
212
|
+
|
213
|
+
One can imagine that when the `interval` is set to 1, users can have a responsive experience of static pages while perceiving real-time data.
|
214
|
+
|
215
|
+
:::info
|
216
|
+
For more detail, see [`<PreRender>`](/apis/app/runtime/ssr/pre-render).
|
217
|
+
|
218
|
+
:::
|
219
|
+
|
220
|
+
## Treeshaking
|
221
|
+
|
222
|
+
When SSR is enabled, Modern.js uses the same entry point to build both SSR Bundle and CSR Bundle. Therefore, errors may occur if there are Web APIs in the SSR Bundle or Node APIs in the CSR Bundle.
|
223
|
+
|
224
|
+
Introducing Web APIs in components usually do some global listening or obtaining browser-related data, such as:
|
225
|
+
|
226
|
+
```tsx
|
227
|
+
document.addEventListener('load', () => {
|
228
|
+
console.log('document load');
|
229
|
+
});
|
230
|
+
const App = () => {
|
231
|
+
return <div>Hello World</div>;
|
232
|
+
};
|
233
|
+
export default App;
|
234
|
+
```
|
235
|
+
|
236
|
+
Importing Node API in component files is usually done when using `useLoader`, for example:
|
237
|
+
|
238
|
+
```ts
|
239
|
+
import fse from 'fs-extra';
|
240
|
+
export default () => {
|
241
|
+
const file = fse.readFileSync('./myfile');
|
242
|
+
return {
|
243
|
+
...
|
244
|
+
};
|
245
|
+
};
|
246
|
+
```
|
247
|
+
|
248
|
+
### Use Environment Variables
|
249
|
+
|
250
|
+
For the first case, we can directly use the built-in environment variable `MODERN_TARGET` in Modern.js to determine and remove unused code during build time:
|
251
|
+
|
252
|
+
```ts
|
253
|
+
if (process.env.MODERN_TARGET === 'browser') {
|
254
|
+
document.addEventListener('load', () => {
|
255
|
+
console.log('document load');
|
256
|
+
});
|
257
|
+
}
|
258
|
+
```
|
259
|
+
|
260
|
+
After the development environment is bundled, the SSR and CSR artifacts will be compiled into the following content. Therefore, there will be no more Web API errors in the SSR environment.
|
261
|
+
|
262
|
+
```ts
|
263
|
+
// SSR production
|
264
|
+
if (false) {
|
265
|
+
}
|
266
|
+
|
267
|
+
// CSR production
|
268
|
+
if (true) {
|
269
|
+
document.addEventListener('load', () => {
|
270
|
+
console.log('document load');
|
271
|
+
});
|
272
|
+
}
|
273
|
+
```
|
274
|
+
|
275
|
+
:::note
|
276
|
+
For more information, see [environment variables](/guides/basic-features/env-vars).
|
277
|
+
|
278
|
+
:::
|
279
|
+
|
280
|
+
### Use File Suffix
|
281
|
+
|
282
|
+
But for example, in the second case, `fs-extra` is imported in the code, which has side effects using Node API internally. If it is directly referenced in the component, it will cause an error when CSR loading.
|
283
|
+
|
284
|
+
Environment variables does not work in this case. Modern.js also supports using files with the `.node.` suffix to distinguish between the packaging files of SSR Bundle and CSR Bundle products.
|
285
|
+
|
286
|
+
You can create a proxy layer by creating files with the same name but different extensions, such as `.ts` and `.node.ts`:
|
287
|
+
|
288
|
+
```ts title="compat.ts"
|
289
|
+
export const readFileSync: any = () => {};
|
290
|
+
```
|
291
|
+
|
292
|
+
```ts title="compat.node.ts"
|
293
|
+
export { readFileSync } from 'fs-extra';
|
294
|
+
```
|
295
|
+
|
296
|
+
Import `./compat` directly in the file. In SSR environment, files with `.node.ts` suffix will be used first, while in CSR environment, files with `.ts` suffix will be used.
|
297
|
+
|
298
|
+
```ts title="App.tsx"
|
299
|
+
import { readFileSync } from './compat'
|
300
|
+
|
301
|
+
export const loader = () => {
|
302
|
+
const file = readFileSync('./myfile');
|
303
|
+
return {
|
304
|
+
...
|
305
|
+
};
|
306
|
+
};
|
307
|
+
```
|
308
|
+
|
309
|
+
### Independent File
|
310
|
+
|
311
|
+
The two methods mentioned above will both bring some mental burden to developers. In real business scenarios, we found that most of the mixed Node/Web code appears in data requests.
|
312
|
+
|
313
|
+
Therefore, Modern.js has designed a [Data Fetch](/guides/basic-features/data/data-fetch) to separate CSR and SSR code based on [Nested Routing](/guides/basic-features/routes).
|
314
|
+
|
315
|
+
We can separate **data requests from component code** by using independent files. Write the component logic in `routes/page.tsx` and write the data request logic in `routes/page.data.ts`.
|
316
|
+
|
317
|
+
```ts title="routes/page.tsx"
|
318
|
+
export default Page = () => {
|
319
|
+
return <div>Hello World<div>
|
320
|
+
}
|
321
|
+
```
|
322
|
+
|
323
|
+
```ts title="routes/page.data.tsx"
|
324
|
+
import fse from 'fs-extra';
|
325
|
+
export const loader = () => {
|
326
|
+
const file = fse.readFileSync('./myfile');
|
327
|
+
return {
|
328
|
+
...
|
329
|
+
};
|
330
|
+
}
|
331
|
+
```
|
332
|
+
|
333
|
+
## Remote Request
|
334
|
+
|
335
|
+
When initiating interface requests in SSR, developers sometimes encapsulate isomorphic request tools themselves. For some interfaces that require passing user cookies, developers can use the ['useRuntimeContext'](/guides/basic-features/data/data-fetch#route-loader) API to get the request header for implementation.
|
336
|
+
|
337
|
+
It should be noted that the obtained request header is for HTML requests, which may not be suitable for API requests. Therefore, ** don't passed through all request headers **.
|
338
|
+
|
339
|
+
In addition, some backend interfaces or common gateways will verify based on the information in the request header. Full pass-through can easily lead to various difficult-to-troubleshoot issues, so it is recommended to **pass through as needed**.
|
340
|
+
|
341
|
+
If it is really necessary to pass through all request headers, please be sure to filter the `host` field.
|
@@ -22,7 +22,7 @@ Please refer to [Modern.js Builder - Using PostCSS](https://modernjs.dev/builder
|
|
22
22
|
|
23
23
|
## Using CSS Modules
|
24
24
|
|
25
|
-
Please read the [Using CSS Modules](
|
25
|
+
Please read the [Using CSS Modules](/guides/basic-features/css-modules) section to learn about the complete usage of CSS Modules.
|
26
26
|
|
27
27
|
## Using CSS-in-JS
|
28
28
|
|
@@ -72,18 +72,7 @@ To use [Tailwind CSS](https://tailwindcss.com/) in Modern.js, you can follow the
|
|
72
72
|
? Please select the feature name: Enable Tailwind CSS
|
73
73
|
```
|
74
74
|
|
75
|
-
After successful initialization, you will see
|
76
|
-
|
77
|
-
```json title="./package.json"
|
78
|
-
{
|
79
|
-
"dependencies": {
|
80
|
-
"tailwindcss": "^3.0.0"
|
81
|
-
},
|
82
|
-
"devDependencies": {
|
83
|
-
"@modern-js/plugin-tailwindcss": "^2.0.0"
|
84
|
-
}
|
85
|
-
}
|
86
|
-
```
|
75
|
+
After successful initialization, you will see that the `package.json` has added dependencies for `tailwindcss` and `@modern-js/plugin-tailwindcss`.
|
87
76
|
|
88
77
|
2. Register the Tailwind plugin in `modern.config.ts`:
|
89
78
|
|
@@ -97,7 +97,7 @@ Modern.js supports three CSS preprocessors: [Sass](https://sass-lang.com/), [Les
|
|
97
97
|
|
98
98
|
Modern.js provides out-of-the-box support for [CSS Modules](https://github.com/css-modules/css-modules), which is implemented internally based on [css-loader](https://www.npmjs.com/package/css-loader).
|
99
99
|
|
100
|
-
Please refer to ["Use CSS Modules"](
|
100
|
+
Please refer to ["Use CSS Modules"](/guides/basic-features/css-modules) for usage instructions.
|
101
101
|
|
102
102
|
---
|
103
103
|
|
@@ -0,0 +1,189 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 3
|
3
|
+
title: 缓存
|
4
|
+
---
|
5
|
+
|
6
|
+
# 渲染缓存
|
7
|
+
|
8
|
+
有时我们会将计算结果进行缓存,例如 React useMemo, useCallback Hook。
|
9
|
+
通过缓存我们可以减少计算的次数来减少 CPU 资源占用,提高用户体验。
|
10
|
+
|
11
|
+
通过将服务器端渲染(SSR)结果进行缓存能够减少服务器每次请求时的计算和渲染时间,从而加速页面加载速度,提高用户体验。
|
12
|
+
降低服务端负载,节省计算资源,提高用户访问速度。
|
13
|
+
|
14
|
+
:::info
|
15
|
+
需要 x.43.0+
|
16
|
+
:::
|
17
|
+
|
18
|
+
## 配置方式
|
19
|
+
|
20
|
+
在 `server/cache.[t|j]s` 中配置缓存即可开启缓存:
|
21
|
+
|
22
|
+
```ts title="server/cache.ts"
|
23
|
+
import type { CacheOption } from '@modern-js/runtime/server';
|
24
|
+
|
25
|
+
export const cacheOption: CacheOption = {
|
26
|
+
maxAge: 500, // ms
|
27
|
+
staleWhileRevalidate: 1000, // ms
|
28
|
+
};
|
29
|
+
```
|
30
|
+
|
31
|
+
## 配置说明
|
32
|
+
|
33
|
+
### 缓存配置
|
34
|
+
|
35
|
+
缓存策略参考 [stale-while-revalidate](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) 进行实现。
|
36
|
+
|
37
|
+
在 `maxAge` 时间内会直接返回缓存内容,超过 `maxAge` 但在 `staleWhileRevalidate` 内也会直接返回缓存内容,但同时会异步做一次重新渲染。
|
38
|
+
|
39
|
+
**Object 类型**
|
40
|
+
|
41
|
+
```ts
|
42
|
+
export interface CacheControl {
|
43
|
+
maxAge: number;
|
44
|
+
|
45
|
+
staleWhileRevalidate: number;
|
46
|
+
|
47
|
+
customKey?: string | ((pathname: string) => string);
|
48
|
+
}
|
49
|
+
```
|
50
|
+
|
51
|
+
其中 customKey 为自定义缓存 key。默认情况下 Modern.js 将会把请求 pathname 作为 key 进行缓存,但在某些情况下这不能满足你的需求,开发者可以进行自定义。
|
52
|
+
|
53
|
+
**Function 类型**
|
54
|
+
|
55
|
+
```ts
|
56
|
+
export type CacheOptionProvider = (
|
57
|
+
req: IncomingMessage,
|
58
|
+
) => Promise<CacheControl> | CacheControl;
|
59
|
+
```
|
60
|
+
|
61
|
+
有时开发者需要通过 req 来自定义缓存 key,可以配置为函数的形式进行处理, 例如以下代码:
|
62
|
+
|
63
|
+
```ts title="server/cache.ts"
|
64
|
+
|
65
|
+
import type { CacheOption, CacheOptionProvider } from '@modern-js/runtime/server';
|
66
|
+
|
67
|
+
const provider: CacheOptionProvider = (req) => {
|
68
|
+
const { url, headers, ... } = req;
|
69
|
+
|
70
|
+
const key = computedKey(url, headers, ...);
|
71
|
+
|
72
|
+
return {
|
73
|
+
maxAge: 500, // ms
|
74
|
+
staleWhileRevalidate: 1000, // ms
|
75
|
+
customKey: key,
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
export const cacheOption: CacheOption = provider;
|
80
|
+
```
|
81
|
+
|
82
|
+
**Mapping 类型**
|
83
|
+
|
84
|
+
```ts
|
85
|
+
export type CacheOptions = Record<string, CacheControl | CacheOptionProvider>;
|
86
|
+
```
|
87
|
+
|
88
|
+
有时开发者面对不同的路由需要应用不同的缓存策略。我们也提供一种映射的方式进行配置, 以下列代码为例子
|
89
|
+
|
90
|
+
```ts title="server/cache.ts"
|
91
|
+
import type { CacheOption } from '@modern-js/runtime/server';
|
92
|
+
|
93
|
+
export const cacheOption: CacheOption = {
|
94
|
+
'/home': {
|
95
|
+
maxAge: 50,
|
96
|
+
staleWhileRevalidate: 100,
|
97
|
+
},
|
98
|
+
'/about': {
|
99
|
+
maxAge: 1000 * 60 * 60 * 24, // one day
|
100
|
+
staleWhileRevalidate: 1000 * 60 * 60 * 24 * 2 // two day
|
101
|
+
},
|
102
|
+
'*': (req) => { // 若上述路由无法匹配,则会匹配到 '*'
|
103
|
+
const { url, headers, ... } = req;
|
104
|
+
const key = computedKey(url, headers, ...);
|
105
|
+
|
106
|
+
return {
|
107
|
+
maxAge: 500,
|
108
|
+
staleWhileRevalidate: 1000,
|
109
|
+
customKey: key,
|
110
|
+
}
|
111
|
+
}
|
112
|
+
}
|
113
|
+
```
|
114
|
+
|
115
|
+
- 路由 `http://xxx/home` 将会应用第一条规则。
|
116
|
+
- 路由 `http://xxx/about` 将会应用第二条规则。
|
117
|
+
- 路由 `http://xxx/abc` 将会应用最后一条规则。
|
118
|
+
|
119
|
+
上述 `/home` 和 `/about` 将会作为模式进行匹配,这意味着 `/home/abc` 也会匹配上该规则。
|
120
|
+
同时,你也可以在其中编写正则语法:`/home/.+`
|
121
|
+
|
122
|
+
### 缓存容器
|
123
|
+
|
124
|
+
默认情况下,Server 将会使用内存进行缓存。但通常情况下服务将会部署在 serverless 上。每一次的服务访问可能都是一个新的进程,这样每次访问都不能应用缓存
|
125
|
+
|
126
|
+
故开发者也可以自定义缓存容器,容器需实现接口 `Containter`
|
127
|
+
|
128
|
+
```ts
|
129
|
+
export interface Containter<K = string, V = string> {
|
130
|
+
/**
|
131
|
+
* Returns a specified element from the containter. 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 Containter.
|
132
|
+
* @returns Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
|
133
|
+
*/
|
134
|
+
get: (key: K) => Promise<V | undefined>;
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Adds a new element with a specified key and value to the containter. If an element with the same key already exists, the element will be updated.
|
138
|
+
*
|
139
|
+
* The ttl indicates cache expiration time.
|
140
|
+
*/
|
141
|
+
set: (key: K, value: V, options?: { ttl?: number }) => Promise<this>;
|
142
|
+
|
143
|
+
/**
|
144
|
+
* @returns boolean indicating whether an element with the specified key exists or not.
|
145
|
+
*/
|
146
|
+
has: (key: K) => Promise<boolean>;
|
147
|
+
|
148
|
+
/**
|
149
|
+
* @returns true if an element in the containter existed and has been removed, or false if the element does not exist.
|
150
|
+
*/
|
151
|
+
delete: (key: K) => Promise<boolean>;
|
152
|
+
}
|
153
|
+
```
|
154
|
+
|
155
|
+
以下面代码为例,开发者可实现一个 redis 容器。
|
156
|
+
|
157
|
+
```ts
|
158
|
+
import type { Containter, CacheOption } from '@modern-js/runtime/server';
|
159
|
+
|
160
|
+
class RedisContainter implements Containter {
|
161
|
+
redis = new Redis();
|
162
|
+
|
163
|
+
async get(key: string) {
|
164
|
+
return this.redis.get(key);
|
165
|
+
}
|
166
|
+
|
167
|
+
async set(key: string, value: string): Promise<this> {
|
168
|
+
this.redis.set(key, value);
|
169
|
+
return this;
|
170
|
+
}
|
171
|
+
|
172
|
+
async has(key: string): Promise<boolean> {
|
173
|
+
return this.redis.has(key);
|
174
|
+
}
|
175
|
+
|
176
|
+
async delete(key: string): Promise<boolean> {
|
177
|
+
return this.redis.delete(key);
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
const containter = new RedisContainter();
|
182
|
+
|
183
|
+
export const customContainer: Containter = containter;
|
184
|
+
|
185
|
+
export const cacheOption: CacheOption = {
|
186
|
+
maxAge: 500, // ms
|
187
|
+
staleWhileRevalidate: 1000, // ms
|
188
|
+
};
|
189
|
+
```
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# 服务端渲染
|
2
|
+
|
3
|
+
通过在服务器端将网页的 HTML 内容渲染成完整的网页,然后将生成的网页发送到客户端,客户端只需要显示网页即可,不需要再进行额外的渲染。
|
4
|
+
|
5
|
+
它主要的优势在于
|
6
|
+
|
7
|
+
- 提高首屏加载速度:SSR 可以在服务器端生成完整的网页,客户端只需要下载网页内容即可,不需要再进行额外的渲染,从而提高了首屏加载速度。
|
8
|
+
- 提高用户体验:SSR 可以提高网页的响应速度,从而提高用户体验。
|
9
|
+
- 有利于 SEO:SSR 可以生成完整的 HTML 内容,搜索引擎可以直接索引 HTML 内容,从而提高网站的排名。
|
10
|
+
|
11
|
+
如果你有以下场景的需求,开发者可以考虑使用 SSR 来渲染你的页面:
|
12
|
+
|
13
|
+
1. 对首屏加载速度要求较高的网站,如电商网站、新闻网站等。
|
14
|
+
2. 对用户体验要求较高的网站,如社交网站、游戏网站等。
|
15
|
+
3. 对 SEO 要求较高的网站,如企业官网、博客等。
|
16
|
+
|
17
|
+
在 Modern.js 中,SSR 也是开箱即用的。开发者无需为 SSR 编写复杂的服务端逻辑,也无需关心 SSR 的运维,或是创建单独的服务。
|
18
|
+
除了开箱即用的 SSR 服务,为了保证开发者的开发体验,我们还具备:
|
19
|
+
|
20
|
+
- 完备的 SSR 降级策略,保证页面能够安全运行。
|
21
|
+
- 自动分割子路由,按需加载,减少首屏资源体积。
|
22
|
+
- 内置缓存系统,解决服务端负载高的问题。
|