@modern-js/main-doc 2.58.2 → 2.59.0
Sign up to get free protection for your applications and to get access to all the features.
- package/docs/en/apis/app/runtime/core/use-loader.mdx +1 -1
- package/docs/en/community/blog/_meta.json +1 -6
- package/docs/en/components/deploy.mdx +1 -1
- package/docs/en/components/init-app.mdx +0 -1
- package/docs/en/components/init-rspack-app.mdx +0 -1
- package/docs/en/components/ssr-monitor.mdx +3 -0
- package/docs/en/configure/_meta.json +1 -1
- package/docs/en/configure/app/output/ssg.mdx +52 -141
- package/docs/en/configure/app/tools/swc.mdx +1 -1
- package/docs/en/configure/app/tools/tailwindcss.mdx +1 -1
- package/docs/en/guides/advanced-features/_meta.json +0 -8
- package/docs/en/guides/advanced-features/bff/_meta.json +1 -6
- package/docs/en/guides/advanced-features/rsbuild-plugin.mdx +2 -2
- package/docs/en/guides/advanced-features/rspack-start.mdx +7 -22
- package/docs/en/guides/basic-features/_meta.json +31 -9
- package/docs/en/guides/basic-features/css/_meta.json +1 -0
- package/docs/en/guides/basic-features/css/css-in-js.mdx +34 -0
- package/docs/en/guides/basic-features/{css-modules.mdx → css/css-modules.mdx} +0 -4
- package/docs/en/guides/basic-features/css/css.mdx +25 -0
- package/docs/en/guides/basic-features/{css.mdx → css/tailwindcss.mdx} +5 -66
- package/docs/en/guides/basic-features/data/_meta.json +1 -4
- 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/debug/_meta.json +1 -0
- package/docs/en/guides/basic-features/debug/rsdoctor.mdx +57 -0
- package/docs/en/guides/{advanced-features → basic-features/debug}/using-storybook.mdx +2 -0
- 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/basic-features/static-assets/_meta.json +1 -0
- package/docs/en/guides/basic-features/static-assets.mdx +1 -1
- package/docs/en/guides/basic-features/testing/_meta.json +1 -0
- package/docs/en/guides/basic-features/testing/cypress.mdx +95 -0
- package/docs/en/guides/basic-features/testing/jest.mdx +148 -0
- package/docs/en/guides/basic-features/testing/playwright.mdx +111 -0
- package/docs/en/guides/basic-features/testing/vitest.mdx +100 -0
- package/docs/en/guides/concept/_meta.json +1 -4
- package/docs/en/guides/concept/entries.mdx +78 -47
- package/docs/en/guides/get-started/_meta.json +1 -7
- package/docs/en/guides/get-started/introduction.mdx +1 -1
- package/docs/en/guides/get-started/quick-start.mdx +1 -2
- package/docs/en/guides/get-started/tech-stack.mdx +4 -6
- package/docs/en/guides/get-started/upgrade.mdx +16 -2
- package/docs/en/guides/topic-detail/framework-plugin/_meta.json +1 -1
- package/docs/en/guides/topic-detail/generator/_meta.json +1 -1
- package/docs/en/guides/topic-detail/generator/create/_meta.json +1 -5
- package/docs/en/guides/topic-detail/generator/create/config.mdx +0 -10
- package/docs/en/guides/topic-detail/generator/create/use.mdx +0 -1
- package/docs/en/guides/topic-detail/generator/new/_meta.json +1 -5
- package/docs/en/guides/topic-detail/generator/plugin/_meta.json +1 -1
- package/docs/en/guides/troubleshooting/_meta.json +1 -6
- package/docs/en/tutorials/first-app/c03-css.mdx +1 -1
- package/docs/zh/apis/app/runtime/core/use-loader.mdx +1 -1
- package/docs/zh/community/blog/_meta.json +1 -6
- package/docs/zh/components/deploy.mdx +1 -1
- package/docs/zh/components/init-app.mdx +0 -1
- package/docs/zh/components/init-rspack-app.mdx +0 -1
- package/docs/zh/components/ssr-monitor.mdx +3 -0
- package/docs/zh/configure/_meta.json +1 -1
- package/docs/zh/configure/app/output/ssg.mdx +49 -139
- package/docs/zh/configure/app/tools/swc.mdx +1 -1
- package/docs/zh/configure/app/tools/tailwindcss.mdx +1 -1
- package/docs/zh/guides/advanced-features/_meta.json +0 -8
- package/docs/zh/guides/advanced-features/bff/_meta.json +1 -6
- package/docs/zh/guides/advanced-features/rsbuild-plugin.mdx +2 -2
- package/docs/zh/guides/advanced-features/rspack-start.mdx +8 -24
- package/docs/zh/guides/basic-features/_meta.json +31 -9
- package/docs/zh/guides/basic-features/css/_meta.json +1 -0
- package/docs/zh/guides/basic-features/css/css-in-js.mdx +34 -0
- package/docs/zh/guides/basic-features/css/css.mdx +25 -0
- package/docs/zh/guides/basic-features/{css.mdx → css/tailwindcss.mdx} +3 -64
- package/docs/zh/guides/basic-features/data/_meta.json +1 -4
- package/docs/zh/guides/basic-features/data/data-fetch.mdx +98 -214
- package/docs/zh/guides/basic-features/data/data-write.mdx +54 -55
- package/docs/zh/guides/basic-features/debug/_meta.json +1 -0
- package/docs/zh/guides/basic-features/debug/rsdoctor.mdx +57 -0
- package/docs/zh/guides/{advanced-features → basic-features/debug}/using-storybook.mdx +1 -1
- 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/basic-features/static-assets/_meta.json +1 -0
- package/docs/zh/guides/basic-features/static-assets.mdx +2 -6
- package/docs/zh/guides/basic-features/testing/_meta.json +1 -0
- package/docs/zh/guides/basic-features/testing/cypress.mdx +95 -0
- package/docs/zh/guides/basic-features/testing/jest.mdx +148 -0
- package/docs/zh/guides/basic-features/testing/playwright.mdx +112 -0
- package/docs/zh/guides/basic-features/testing/vitest.mdx +100 -0
- package/docs/zh/guides/concept/_meta.json +1 -4
- package/docs/zh/guides/concept/entries.mdx +80 -58
- package/docs/zh/guides/get-started/_meta.json +1 -7
- package/docs/zh/guides/get-started/introduction.mdx +2 -2
- package/docs/zh/guides/get-started/quick-start.mdx +1 -2
- package/docs/zh/guides/get-started/tech-stack.mdx +8 -10
- package/docs/zh/guides/get-started/upgrade.mdx +15 -1
- package/docs/zh/guides/topic-detail/framework-plugin/_meta.json +1 -1
- package/docs/zh/guides/topic-detail/generator/_meta.json +1 -1
- package/docs/zh/guides/topic-detail/generator/create/_meta.json +1 -5
- package/docs/zh/guides/topic-detail/generator/create/config.mdx +0 -10
- package/docs/zh/guides/topic-detail/generator/create/use.mdx +0 -1
- package/docs/zh/guides/topic-detail/generator/new/_meta.json +1 -5
- package/docs/zh/guides/topic-detail/generator/plugin/_meta.json +1 -1
- package/docs/zh/guides/troubleshooting/_meta.json +1 -6
- package/docs/zh/tutorials/first-app/c03-css.mdx +1 -1
- package/i18n.json +16 -4
- package/package.json +6 -6
- package/rspress.config.ts +1 -1
- package/src/components/ContentCard/index.tsx +1 -1
- package/src/components/Sandpack/index.tsx +1 -1
- package/src/components/ShowcaseList/index.tsx +1 -1
- package/src/i18n/index.ts +1 -1
- package/src/pages/index.tsx +2 -2
- package/docs/en/apis/app/hooks/config/storybook.mdx +0 -37
- package/docs/en/guides/advanced-features/ssg.mdx +0 -116
- package/docs/en/guides/advanced-features/ssr/_meta.json +0 -5
- 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/apis/app/hooks/config/storybook.mdx +0 -38
- package/docs/zh/guides/advanced-features/ssg.mdx +0 -116
- package/docs/zh/guides/advanced-features/ssr/_meta.json +0 -5
- package/docs/zh/guides/advanced-features/ssr/index.mdx +0 -23
- package/docs/zh/guides/advanced-features/ssr/usage.mdx +0 -329
- /package/docs/en/guides/basic-features/{mock.mdx → debug/mock.mdx} +0 -0
- /package/docs/en/guides/basic-features/{proxy.mdx → debug/proxy.mdx} +0 -0
- /package/docs/en/guides/basic-features/{json-files.mdx → static-assets/json-files.mdx} +0 -0
- /package/docs/en/guides/basic-features/{svg-assets.mdx → static-assets/svg-assets.mdx} +0 -0
- /package/docs/en/guides/basic-features/{wasm-assets.mdx → static-assets/wasm-assets.mdx} +0 -0
- /package/docs/zh/guides/basic-features/{css-modules.mdx → css/css-modules.mdx} +0 -0
- /package/docs/zh/guides/basic-features/{mock.mdx → debug/mock.mdx} +0 -0
- /package/docs/zh/guides/basic-features/{proxy.mdx → debug/proxy.mdx} +0 -0
- /package/docs/zh/guides/basic-features/{json-files.mdx → static-assets/json-files.mdx} +0 -0
- /package/docs/zh/guides/basic-features/{svg-assets.mdx → static-assets/svg-assets.mdx} +0 -0
- /package/docs/zh/guides/basic-features/{wasm-assets.mdx → static-assets/wasm-assets.mdx} +0 -0
@@ -0,0 +1,230 @@
|
|
1
|
+
# Streaming SSR
|
2
|
+
|
3
|
+
Streaming SSR is a new rendering method that updates the page content in real-time as users interact with it, enhancing user experience.
|
4
|
+
|
5
|
+
In conventional rendering, the page is rendered all at once. In streaming rendering, the page is rendered progressively, loading data step-by-step as users interact with the page instead of loading all data at once.
|
6
|
+
|
7
|
+
Compared to traditional rendering:
|
8
|
+
|
9
|
+
- **Faster Perceived Speed**: Streaming rendering can progressively display content, quickly rendering the home page.
|
10
|
+
- **Enhanced User Experience**: Users can see page content faster and interact without waiting for the entire page to render.
|
11
|
+
- **Better Performance Control**: Developers can better control the loading priority and order, optimizing performance and user experience.
|
12
|
+
- **Better Adaptability**: Streaming rendering adapts better to various network speeds and device performance, ensuring good performance across different environments.
|
13
|
+
|
14
|
+
## Enabling Streaming Rendering
|
15
|
+
|
16
|
+
Modern.js supports React 18's streaming rendering, which can be enabled as follows:
|
17
|
+
|
18
|
+
```ts title="modern.config.ts"
|
19
|
+
import { defineConfig } from '@modern-js/app-tools';
|
20
|
+
|
21
|
+
export default defineConfig({
|
22
|
+
server: {
|
23
|
+
ssr: {
|
24
|
+
mode: 'stream',
|
25
|
+
},
|
26
|
+
},
|
27
|
+
});
|
28
|
+
```
|
29
|
+
|
30
|
+
Modern.js streaming rendering is based on React Router and involves several key APIs:
|
31
|
+
|
32
|
+
- [`defer`](https://reactrouter.com/en/main/utils/defer): Used in Data Loader to support asynchronous data fetching.
|
33
|
+
- [`Await`](https://reactrouter.com/en/main/components/await): Used to render the asynchronous data returned by the Data Loader.
|
34
|
+
- [`useAsyncValue`](https://reactrouter.com/en/main/hooks/use-async-value): Used to fetch data from the nearest parent `Await` component.
|
35
|
+
|
36
|
+
## Fetching Data
|
37
|
+
|
38
|
+
```ts title="user/[id]/page.data.ts"
|
39
|
+
import { defer, type LoaderFunctionArgs } from '@modern-js/runtime/router';
|
40
|
+
|
41
|
+
interface User {
|
42
|
+
name: string;
|
43
|
+
age: number;
|
44
|
+
}
|
45
|
+
|
46
|
+
export interface Data {
|
47
|
+
data: User;
|
48
|
+
}
|
49
|
+
|
50
|
+
export const loader = ({ params }: LoaderFunctionArgs) => {
|
51
|
+
const userId = params.id;
|
52
|
+
|
53
|
+
const user = new Promise<User>(resolve => {
|
54
|
+
setTimeout(() => {
|
55
|
+
resolve({
|
56
|
+
name: `user-${userId}`,
|
57
|
+
age: 18,
|
58
|
+
});
|
59
|
+
}, 200);
|
60
|
+
});
|
61
|
+
|
62
|
+
return defer({ data: user });
|
63
|
+
};
|
64
|
+
```
|
65
|
+
|
66
|
+
Here, `user` is a Promise object representing asynchronously fetched data, processed using `defer`. Notice that `defer` must receive an object parameter; a direct Promise cannot be passed.
|
67
|
+
|
68
|
+
Additionally, `defer` can receive both asynchronous and synchronous data. In the example below, short-duration requests are returned using object data, while longer-duration requests are returned using a Promise:
|
69
|
+
|
70
|
+
```ts title="user/[id]/page.data.ts"
|
71
|
+
export const loader = ({ params }: LoaderFunctionArgs) => {
|
72
|
+
const userId = params.id;
|
73
|
+
|
74
|
+
const user = new Promise<User>(resolve => {
|
75
|
+
setTimeout(() => {
|
76
|
+
resolve({
|
77
|
+
name: `user-${userId}`,
|
78
|
+
age: 18,
|
79
|
+
});
|
80
|
+
}, 2000);
|
81
|
+
});
|
82
|
+
|
83
|
+
const otherData = new Promise<string>(resolve => {
|
84
|
+
setTimeout(() => {
|
85
|
+
resolve('some sync data');
|
86
|
+
}, 200);
|
87
|
+
});
|
88
|
+
|
89
|
+
return defer({
|
90
|
+
data: user,
|
91
|
+
other: await otherData,
|
92
|
+
});
|
93
|
+
};
|
94
|
+
```
|
95
|
+
|
96
|
+
This way, the application can prioritize displaying partially available content without waiting for the most time-consuming data requests.
|
97
|
+
|
98
|
+
## Rendering Data
|
99
|
+
|
100
|
+
To render the asynchronous data returned by the Data Loader, use the `Await` component. For example:
|
101
|
+
|
102
|
+
```tsx title="user/[id]/page.tsx"
|
103
|
+
import { Await, useLoaderData } from '@modern-js/runtime/router';
|
104
|
+
import { Suspense } from 'react';
|
105
|
+
import type { Data } from './page.data';
|
106
|
+
|
107
|
+
const Page = () => {
|
108
|
+
const data = useLoaderData() as Data;
|
109
|
+
|
110
|
+
return (
|
111
|
+
<div>
|
112
|
+
User info:
|
113
|
+
<Suspense fallback={<div id="loading">loading user data ...</div>}>
|
114
|
+
<Await resolve={data.data}>
|
115
|
+
{user => {
|
116
|
+
return (
|
117
|
+
<div id="data">
|
118
|
+
name: {user.name}, age: {user.age}
|
119
|
+
</div>
|
120
|
+
);
|
121
|
+
}}
|
122
|
+
</Await>
|
123
|
+
</Suspense>
|
124
|
+
</div>
|
125
|
+
);
|
126
|
+
};
|
127
|
+
|
128
|
+
export default Page;
|
129
|
+
```
|
130
|
+
|
131
|
+
The `Await` component needs to be wrapped inside a `Suspense` component. The `resolve` prop of `Await` should be the asynchronously fetched data from the Data Loader. When the data is fetched, it will be rendered using the [Render Props](https://zh-hans.react.dev/reference/react/cloneElement#passing-data-with-a-render-prop) pattern. During data fetching, the content set by the `fallback` prop of `Suspense` is displayed.
|
132
|
+
|
133
|
+
:::warning Warning
|
134
|
+
When importing types from the `page.data.ts` file, use `import type` to ensure only type information is imported, preventing Data Loader code from being bundled into the frontend.
|
135
|
+
:::
|
136
|
+
|
137
|
+
In the component, you can also fetch asynchronous data returned by the Data Loader using `useAsyncValue`. For example:
|
138
|
+
|
139
|
+
```tsx title='page.tsx'
|
140
|
+
import { useAsyncValue } from '@modern-js/runtime/router';
|
141
|
+
|
142
|
+
const UserInfo = () => {
|
143
|
+
const user = useAsyncValue();
|
144
|
+
return (
|
145
|
+
<div>
|
146
|
+
name: {user.name}, age: {user.age}
|
147
|
+
</div>
|
148
|
+
);
|
149
|
+
};
|
150
|
+
|
151
|
+
const Page = () => {
|
152
|
+
const data = useLoaderData() as Data;
|
153
|
+
return (
|
154
|
+
<div>
|
155
|
+
User info:
|
156
|
+
<Suspense fallback={<div id="loading">loading user data ...</div>}>
|
157
|
+
<Await resolve={data.data}>
|
158
|
+
<UserInfo />
|
159
|
+
</Await>
|
160
|
+
</Suspense>
|
161
|
+
</div>
|
162
|
+
);
|
163
|
+
};
|
164
|
+
|
165
|
+
export default Page;
|
166
|
+
```
|
167
|
+
|
168
|
+
## Error Handling
|
169
|
+
|
170
|
+
The `errorElement` prop of the `Await` component handles errors in Data Loader or sub-component rendering. For example, intentionally throwing an error in the Data Loader function:
|
171
|
+
|
172
|
+
```ts title="page.loader.ts"
|
173
|
+
import { defer } from '@modern-js/runtime/router';
|
174
|
+
|
175
|
+
export default () => {
|
176
|
+
const data = new Promise((resolve, reject) => {
|
177
|
+
setTimeout(() => {
|
178
|
+
reject(new Error('error occurs'));
|
179
|
+
}, 200);
|
180
|
+
});
|
181
|
+
|
182
|
+
return defer({ data });
|
183
|
+
};
|
184
|
+
```
|
185
|
+
|
186
|
+
Then, fetch the error using `useAsyncError` and set a component to render the error message for the `errorElement` prop of the `Await` component:
|
187
|
+
|
188
|
+
```tsx title="page.ts"
|
189
|
+
import { Await, useAsyncError, useLoaderData } from '@modern-js/runtime/router';
|
190
|
+
import { Suspense } from 'react';
|
191
|
+
|
192
|
+
export default function Page() {
|
193
|
+
const data = useLoaderData();
|
194
|
+
|
195
|
+
return (
|
196
|
+
<div>
|
197
|
+
Error page
|
198
|
+
<Suspense fallback={<div>loading ...</div>}>
|
199
|
+
<Await resolve={data.data} errorElement={<ErrorElement />}>
|
200
|
+
{(data: any) => {
|
201
|
+
return <div>never displayed</div>;
|
202
|
+
}}
|
203
|
+
</Await>
|
204
|
+
</Suspense>
|
205
|
+
</div>
|
206
|
+
);
|
207
|
+
}
|
208
|
+
|
209
|
+
function ErrorElement() {
|
210
|
+
const error = useAsyncError() as Error;
|
211
|
+
return <p>Something went wrong! {error.message}</p>;
|
212
|
+
}
|
213
|
+
```
|
214
|
+
|
215
|
+
## Waiting for All Content to Load for Crawlers
|
216
|
+
|
217
|
+
Streaming can enhance user experience by allowing users to perceive content as it becomes available.
|
218
|
+
|
219
|
+
However, when a crawler visits the page, it might need to load all content and output the entire HTML at once, rather than progressively loading it.
|
220
|
+
|
221
|
+
Modern.js uses [isbot](https://www.npmjs.com/package/isbot) to determine if a request is from a crawler based on the `user-agent` header.
|
222
|
+
|
223
|
+
import StreamSSRPerformance from '@site-docs/components/stream-ssr-performance';
|
224
|
+
|
225
|
+
<StreamSSRPerformance />
|
226
|
+
|
227
|
+
## Related Documentation
|
228
|
+
|
229
|
+
1. [Deferred Data](https://reactrouter.com/en/main/guides/deferred)
|
230
|
+
2. [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37)
|