@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
@@ -1,32 +1,18 @@
|
|
1
1
|
---
|
2
|
-
title: Data Fetching
|
3
2
|
sidebar_position: 3
|
4
3
|
---
|
5
4
|
|
6
5
|
# Data Fetching
|
7
6
|
|
8
|
-
Modern.js provides out-of-the-box data fetching capabilities
|
7
|
+
Modern.js provides out-of-the-box data fetching capabilities. Developers can use these APIs to fetch data in their projects. It's important to note that these APIs do not help the application make requests but assist developers in managing data better and improving project performance.
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
## Data Loader (Recommended)
|
13
|
-
|
14
|
-
Modern.js recommends using [conventional routing](/guides/basic-features/routes) for routing management. Through Modern.js's [conventional (nested) routing](/guides/basic-features/routes#conventional-routing), each routing component (`layout.ts` or `page.ts`) can have a same-named `loader` file. The `loader` file needs to export a function that will be executed before the component is rendered to provide data for the routing component.
|
15
|
-
|
16
|
-
:::info
|
17
|
-
Modern.js v1 supports fetching data via [useLoader](</guides/basic-features/data/data-fetch.html#useloader-(old-version)>), which is no longer the recommended usage. We do not recommend mixing the two except during the migration process.
|
18
|
-
|
19
|
-
:::
|
20
|
-
|
21
|
-
:::warning
|
22
|
-
- In the previous version, Modern.js Data Loader was defined in the `loader` file. In later versions, we recommend defining it in the `data` file, while we will maintain compatibility with the `loader` file.
|
23
|
-
- In the `data` file, the corresponding `loader` needs to be exported with a name。
|
9
|
+
## What is Data Loader
|
24
10
|
|
11
|
+
:::note
|
12
|
+
In Modern.js v1 projects, data was fetched using `useLoader`. This is no longer the recommended approach; we suggest migrating to Data Loader.
|
25
13
|
:::
|
26
14
|
|
27
|
-
|
28
|
-
|
29
|
-
Routing components such as `layout.ts` or `page.ts` can define a same-named `loader` file. The function exported by the `data` file provides the data required by the component, and then the data is obtained in the routing component through the `useLoaderData` function, as shown in the following example:
|
15
|
+
Modern.js recommends managing routes using [conventional routing](/guides/basic-features/routes). Each route component (`layout.ts`, `page.ts`, or `$.tsx`) can have a same-named `.data` file. These files can export a `loader` function, known as Data Loader, which executes before the corresponding route component renders to provide data for the component. Here is an example:
|
30
16
|
|
31
17
|
```bash
|
32
18
|
.
|
@@ -39,17 +25,7 @@ Routing components such as `layout.ts` or `page.ts` can define a same-named `loa
|
|
39
25
|
└── page.data.ts
|
40
26
|
```
|
41
27
|
|
42
|
-
|
43
|
-
|
44
|
-
```ts title="routes/user/page.tsx"
|
45
|
-
import { useLoaderData } from '@modern-js/runtime/router';
|
46
|
-
import type { ProfileData } from './page.data.ts';
|
47
|
-
|
48
|
-
export default function UserPage() {
|
49
|
-
const profileData = useLoaderData() as ProfileData;
|
50
|
-
return <div>{profileData}</div>;
|
51
|
-
}
|
52
|
-
```
|
28
|
+
In the `routes/user/page.data.ts` file, you can export a Data Loader function:
|
53
29
|
|
54
30
|
```ts title="routes/user/page.data.ts"
|
55
31
|
export type ProfileData = {
|
@@ -62,52 +38,69 @@ export const loader = async (): Promise<ProfileData> => {
|
|
62
38
|
};
|
63
39
|
```
|
64
40
|
|
65
|
-
:::
|
66
|
-
|
41
|
+
:::warning Compatibility
|
42
|
+
- In previous versions, Data Loader was defined in a `.loader` file. In the current version, we recommend defining it in a `.data` file, while maintaining compatibility with `.loader` files.
|
43
|
+
- In `.loader` files, the Data Loader can be exported as default. In `.data` files, it should be named `loader` export.
|
44
|
+
```ts
|
45
|
+
// xxx.loader.ts
|
46
|
+
export default () => {}
|
67
47
|
|
48
|
+
// xxx.data.ts
|
49
|
+
export const loader = () => {}
|
50
|
+
```
|
68
51
|
:::
|
69
52
|
|
70
|
-
In the
|
53
|
+
In the route component, you can use the `useLoaderData` function to fetch data:
|
54
|
+
|
55
|
+
```ts title="routes/user/page.tsx"
|
56
|
+
import { useLoaderData } from '@modern-js/runtime/router';
|
57
|
+
import type { ProfileData } from './page.data.ts';
|
58
|
+
|
59
|
+
export default function UserPage() {
|
60
|
+
const profileData = useLoaderData() as ProfileData;
|
61
|
+
return <div>{profileData}</div>;
|
62
|
+
}
|
63
|
+
```
|
64
|
+
|
65
|
+
:::caution
|
66
|
+
Route components and `.data` files share types. Use `import type` to avoid unexpected side effects.
|
67
|
+
:::
|
71
68
|
|
72
|
-
In
|
69
|
+
In a CSR environment, the `loader` function executes on the browser side and can use browser APIs (though it's usually unnecessary and not recommended).
|
73
70
|
|
74
|
-
|
75
|
-
In future versions, Modern.js may support running the `loader` function on the server in the CSR environment to improve performance and security. Therefore, it is recommended to ensure that the `loader` function is as pure as possible and only used for data fetching scenarios.
|
71
|
+
In an SSR environment, the `loader` function only executes on the server side for initial page loads and when navigating. Here it can call any Node.js APIs, and any dependencies or code used won't be included in the client-side bundle.
|
76
72
|
|
73
|
+
:::tip
|
74
|
+
In future versions, Modern.js may support running `loader` functions on the server side even in CSR environments to improve performance and security. Therefore, it is advisable to keep the `loader` function pure, handling only data fetching scenarios.
|
77
75
|
:::
|
78
76
|
|
79
|
-
When navigating on the client based on
|
77
|
+
When navigating on the client side, based on [conventional routing](/guides/basic-features/routes), Modern.js supports parallel execution (requests) of all `loader` functions. For example, when visiting `/user/profile`, the `loader` functions under `/user` and `/user/profile` will execute in parallel, solving the request-render waterfall issue and significantly improving page performance.
|
80
78
|
|
81
|
-
|
79
|
+
## `loader` Function
|
82
80
|
|
83
|
-
The `loader` function has two
|
81
|
+
The `loader` function has two parameters used for getting route parameters and request information.
|
84
82
|
|
85
|
-
|
83
|
+
### params
|
86
84
|
|
87
|
-
|
85
|
+
`params` is the dynamic route segments when the route is a [dynamic route](/guides/basic-features/routes#dynamic-routes), which passed as parameters to the `loader` function:
|
88
86
|
|
89
|
-
```tsx
|
90
|
-
// routes/user/[id]/page.data.tsx
|
87
|
+
```tsx title="routes/user/[id]/page.data.ts"
|
91
88
|
import { LoaderFunctionArgs } from '@modern-js/runtime/router';
|
92
89
|
|
93
|
-
|
90
|
+
// When visiting /user/123, the function parameter is `{ params: { id: '123' } }`
|
91
|
+
export const loader = async ({ params }: LoaderFunctionArgs) => {
|
94
92
|
const { id } = params;
|
95
93
|
const res = await fetch(`https://api/user/${id}`);
|
96
94
|
return res.json();
|
97
95
|
};
|
98
96
|
```
|
99
97
|
|
100
|
-
|
101
|
-
|
102
|
-
#### `request`
|
98
|
+
### request
|
103
99
|
|
104
|
-
`request` is
|
105
|
-
|
106
|
-
A common usage scenario is to get query parameters through `request`:
|
100
|
+
`request` is an instance of [Fetch Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). A common use case is to get query parameters from `request`:
|
107
101
|
|
108
102
|
```tsx
|
109
|
-
|
110
|
-
import { LoaderFunctionArgs } from '@modern-js/runtime/router';
|
103
|
+
import { LoaderFunctionArgs } from '@modern-js/runtime/router;
|
111
104
|
|
112
105
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
113
106
|
const url = new URL(request.url);
|
@@ -116,9 +109,9 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
116
109
|
};
|
117
110
|
```
|
118
111
|
|
119
|
-
|
112
|
+
### Return Value
|
120
113
|
|
121
|
-
The return value of the `loader` function
|
114
|
+
The return value of the `loader` function **must be one of two data structures**: a serializable data object or an instance of [Fetch Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
|
122
115
|
|
123
116
|
```tsx
|
124
117
|
const loader = async (): Promise<ProfileData> => {
|
@@ -129,7 +122,7 @@ const loader = async (): Promise<ProfileData> => {
|
|
129
122
|
export default loader;
|
130
123
|
```
|
131
124
|
|
132
|
-
By default, the `
|
125
|
+
By default, the `loader` response's `Content-type` is `application/json`, and its `status` is 200. You can customize the `Response` to change these:
|
133
126
|
|
134
127
|
```tsx
|
135
128
|
const loader = async (): Promise<ProfileData> => {
|
@@ -143,25 +136,41 @@ const loader = async (): Promise<ProfileData> => {
|
|
143
136
|
};
|
144
137
|
```
|
145
138
|
|
146
|
-
|
139
|
+
## Using Data Loader in Different Environments
|
140
|
+
|
141
|
+
The `loader` function may run on the server or client. When it runs on the server, it's called a Server Loader; when it runs on the client, it's called a Client Loader.
|
142
|
+
|
143
|
+
In CSR applications, the `loader` function runs on the client, hence it is a Client Loader by default.
|
144
|
+
|
145
|
+
In SSR applications, the `loader` function runs only on the server, hence it is a Server Loader by default. During SSR rendering, Modern.js will directly call the `loader` function on the server side. When navigating on the client side, Modern.js sends an HTTP request to the SSR service, also triggering the `loader` function on the server side.
|
146
|
+
|
147
|
+
:::note
|
148
|
+
Having the `loader` function run only on the server in SSR applications brings several benefits:
|
149
|
+
- **Simplifies usage**: Guarantees consistent data-fetching methods in SSR applications, so developers don't have to distinguish between client and server code.
|
150
|
+
- **Reduces client bundle size**: Moves logic code and dependencies from the client to the server.
|
151
|
+
- **Improves maintainability**: Less direct influence of data logic on front-end UI and avoids issues of accidentally including server dependencies in the client bundle or vice versa.
|
152
|
+
:::
|
147
153
|
|
148
|
-
|
154
|
+
We recommend using the `fetch` API in `loader` functions to make requests. Modern.js provides a default polyfill for the `fetch` API, allowing it to be used on the server. This means you can fetch data in a consistent manner whether in CSR or SSR:
|
149
155
|
|
150
156
|
```tsx
|
151
|
-
function loader() {
|
152
|
-
const res = await fetch('
|
157
|
+
export async function loader() {
|
158
|
+
const res = await fetch('URL_ADDRESS');
|
159
|
+
return {
|
160
|
+
message: res.message
|
161
|
+
}
|
153
162
|
}
|
154
163
|
```
|
155
164
|
|
156
|
-
|
165
|
+
## Error Handling
|
157
166
|
|
158
|
-
|
167
|
+
### Basic Usage
|
159
168
|
|
160
|
-
In
|
169
|
+
In a `loader` function, you can handle errors by using `throw error` or `throw response`. When an error is thrown in the `loader` function, Modern.js will stop executing the remaining code in the current `loader` and switch the front-end UI to the defined [`ErrorBoundary`](/guides/basic-features/routes#error-handling) component:
|
161
170
|
|
162
171
|
```tsx
|
163
|
-
// routes/user/profile/page.data.
|
164
|
-
export
|
172
|
+
// routes/user/profile/page.data.ts
|
173
|
+
export async function loader() {
|
165
174
|
const res = await fetch('https://api/user/profile');
|
166
175
|
if (!res.ok) {
|
167
176
|
throw res;
|
@@ -184,11 +193,11 @@ const ErrorBoundary = () => {
|
|
184
193
|
export default ErrorBoundary;
|
185
194
|
```
|
186
195
|
|
187
|
-
|
196
|
+
### Modify HTTP Code
|
188
197
|
|
189
|
-
In
|
198
|
+
In SSR projects, you can control the page status code by throwing a response in the `loader` function and display the corresponding UI.
|
190
199
|
|
191
|
-
|
200
|
+
In the following example, the page's status code will match this `response`, and the page will display the `ErrorBoundary` UI:
|
192
201
|
|
193
202
|
```ts
|
194
203
|
// routes/user/profile/page.data.ts
|
@@ -201,7 +210,7 @@ export async function loader() {
|
|
201
210
|
}
|
202
211
|
|
203
212
|
// routes/error.tsx
|
204
|
-
import { useRouteError } from '@modern-js/runtime/router
|
213
|
+
import { useRouteError } from '@modern-js/runtime/router;
|
205
214
|
const ErrorBoundary = () => {
|
206
215
|
const error = useRouteError() as { data: string };
|
207
216
|
return <div className="error">{error.data}</div>;
|
@@ -210,16 +219,16 @@ const ErrorBoundary = () => {
|
|
210
219
|
export default ErrorBoundary;
|
211
220
|
```
|
212
221
|
|
213
|
-
|
222
|
+
## Accessing Data from Upper Components
|
214
223
|
|
215
|
-
In many
|
224
|
+
In many scenarios, child components need to access data from the upper component's `loader`. You can use the `useRouteLoaderData` function to easily get data from the upper component:
|
216
225
|
|
217
226
|
```tsx
|
218
227
|
// routes/user/profile/page.tsx
|
219
228
|
import { useRouteLoaderData } from '@modern-js/runtime/router';
|
220
229
|
|
221
|
-
export
|
222
|
-
// Get
|
230
|
+
export function UserLayout() {
|
231
|
+
// Get data returned by the `loader` in routes/user/layout.data.ts
|
223
232
|
const data = useRouteLoaderData('user/layout');
|
224
233
|
return (
|
225
234
|
<div>
|
@@ -230,9 +239,9 @@ export default function UserLayout() {
|
|
230
239
|
}
|
231
240
|
```
|
232
241
|
|
233
|
-
|
242
|
+
`useRouteLoaderData` accepts a parameter `routeId`. In conventional routing, Modern.js automatically generates the `routeId`, which is the path of the corresponding component relative to `src/routes`. In the example above, the `routeId` for fetching data from `routes/user/layout.tsx`'s loader is `user/layout`.
|
234
243
|
|
235
|
-
In a multi-entry
|
244
|
+
In a multi-entry scenario, the `routeId` value needs to include the corresponding entry name, which is typically the directory name if not explicitly specified. For example, with the following directory structure:
|
236
245
|
|
237
246
|
```bash
|
238
247
|
.
|
@@ -245,12 +254,12 @@ In a multi-entry (MPA) scenario, the value of `routeId` needs to include the nam
|
|
245
254
|
└── layout.tsx
|
246
255
|
```
|
247
256
|
|
248
|
-
|
257
|
+
To get data returned by the loader in `entry1/routes/layout.tsx`, the `routeId` value would be `entry1_layout`.
|
249
258
|
|
250
|
-
|
259
|
+
## Loading UI (Experimental)
|
251
260
|
|
252
|
-
:::info
|
253
|
-
This feature is currently experimental and
|
261
|
+
:::info Experimental
|
262
|
+
This feature is currently experimental, and its API may change in the future.
|
254
263
|
:::
|
255
264
|
|
256
265
|
Create `user/layout.data.ts` and add the following code:
|
@@ -269,76 +278,62 @@ export const loader = () =>
|
|
269
278
|
}, 1000);
|
270
279
|
}),
|
271
280
|
});
|
272
|
-
|
273
281
|
```
|
274
282
|
|
275
283
|
Add the following code in `user/layout.tsx`:
|
276
284
|
|
277
285
|
```tsx title="routes/user/layout.tsx"
|
278
|
-
import {
|
279
|
-
Await,
|
280
|
-
defer,
|
281
|
-
useLoaderData,
|
282
|
-
Outlet
|
283
|
-
} from '@modern-js/runtime/router';
|
286
|
+
import { Await, defer, useLoaderData, Outlet } from '@modern-js/runtime/router';
|
284
287
|
|
285
288
|
export default function UserLayout() {
|
286
|
-
const { userInfo } = useLoaderData() as {userInfo: Promise<UserInfo>};
|
289
|
+
const { userInfo } = useLoaderData() as { userInfo: Promise<UserInfo> };
|
287
290
|
return (
|
288
291
|
<div>
|
289
|
-
<React.Suspense
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
292
|
+
<React.Suspense fallback={<p>Loading...</p>}>
|
293
|
+
<Await
|
294
|
+
resolve={userInfo}
|
295
|
+
children={userInfo => (
|
296
|
+
<div>
|
297
|
+
<span>{userInfo.name}</span>
|
298
|
+
<span>{userInfo.age}</span>
|
299
|
+
<Outlet />
|
300
|
+
</div>
|
301
|
+
)}
|
302
|
+
></Await>
|
300
303
|
</React.Suspense>
|
301
304
|
</div>
|
302
305
|
);
|
303
306
|
}
|
304
307
|
```
|
305
308
|
|
306
|
-
:::
|
307
|
-
For details on
|
308
|
-
|
309
|
-
For details on how to use defer, please refer to [defer](https://reactrouter.com/en/main/guides/deferred).
|
309
|
+
:::tip
|
310
|
+
For more details on `<Await>`, refer to the [Await](https://reactrouter.com/en/main/components/await) documentation. For more details on `defer`, refer to the [defer](https://reactrouter.com/en/main/guides/deferred) documentation.
|
310
311
|
:::
|
311
312
|
|
312
|
-
|
313
|
+
## Data Caching
|
313
314
|
|
314
|
-
|
315
|
-
For example, the current route is `a/b`, and the Data Loader corresponding to the `a` path has been executed.
|
316
|
-
When jumping from `/a/b` to `/a/c`, the Data Loader corresponding to `a` path will not be re-executed,
|
317
|
-
and the Data Loader corresponding to `c` path will execute and fetch the data.
|
315
|
+
During route navigation, Modern.js will only load data for the parts of the route that change. For example, if the current route is `a/b`, and the Data Loader for the `a` path has already executed, then when transitioning from `/a/b` to `/a/c`, the Data Loader for the `a` path will not re-execute, but the Data Loader for the `c` path will execute and fetch the data.
|
318
316
|
|
319
|
-
|
320
|
-
and this default optimization strategy avoids invalid duplicate data requests.
|
317
|
+
This default optimization strategy avoids redundant data requests. However, you might wonder how to update the data for the `a` path's Data Loader?
|
321
318
|
|
322
|
-
|
319
|
+
In Modern.js, the Data Loader for a specific path will reload in the following scenarios:
|
323
320
|
|
324
|
-
|
321
|
+
1. After triggering a [Data Action](/guides/basic-features/data/data-write.md)
|
322
|
+
2. When URL parameters change
|
323
|
+
3. When the user clicks a link that matches the current page URL
|
324
|
+
4. When the route component defines a [`shouldRevalidate`](#/shouldrevalidate) function that returns `true`
|
325
325
|
|
326
|
-
|
327
|
-
|
328
|
-
3. The link clicked by the user is the same as the URL of the current page.
|
329
|
-
4. The [`shouldRevalidate`](#/shouldrevalidate) function is defined in the routing component, which returns true
|
330
|
-
|
331
|
-
:::info
|
332
|
-
If you define the [`shouldRevalidate`](#/shouldrevalidate) function on the route, the function will be checked first to determine whether the data needs to be reloaded.
|
326
|
+
:::tip
|
327
|
+
If you define a [`shouldRevalidate`](#/shouldrevalidate) function for a route, this function will be checked first to determine whether data reloads.
|
333
328
|
:::
|
334
329
|
|
335
330
|
### `shouldRevalidate`
|
336
331
|
|
337
332
|
:::warning
|
338
|
-
Currently `shouldRevalidate`
|
333
|
+
Currently, `shouldRevalidate` only takes effect in CSR and Streaming SSR.
|
339
334
|
:::
|
340
335
|
|
341
|
-
In
|
336
|
+
In route components (`layout.tsx`, `page.tsx`, `$.tsx`), you can export a `shouldRevalidate` function. This function is triggered on each route change in the project and can control which route data to reload. If this function returns `true`, Modern.js will reload the corresponding route data.
|
342
337
|
|
343
338
|
```ts title="routes/user/layout.tsx"
|
344
339
|
import type { ShouldRevalidateFunction } from '@modern-js/runtime/router';
|
@@ -358,65 +353,31 @@ export const shouldRevalidate: ShouldRevalidateFunction = ({
|
|
358
353
|
};
|
359
354
|
```
|
360
355
|
|
361
|
-
:::
|
362
|
-
For more details
|
363
|
-
:::
|
364
|
-
|
365
|
-
### Client Loader
|
366
|
-
|
367
|
-
:::info
|
368
|
-
1. This feature requires version x.36.0 or above, and it‘s recommended to use the latest version of the framework.
|
369
|
-
2. Only SSR projects have Client Loaders, while in CSR projects, it can be considered as the default Client Loader.
|
370
|
-
3. This feature can be used progressively and not every project needs it. For specific information, please refer to the explanation of applicable scenarios in the document below.
|
371
|
-
|
372
|
-
:::
|
373
|
-
|
374
|
-
#### Applicable scenarios
|
375
|
-
|
376
|
-
In the SSR project, the code in Data Loader will only be executed on the server side when the client performs SPA navigation.
|
377
|
-
The framework will send an HTTP request to the SSR service to trigger the execution of Data Loader.
|
378
|
-
However, in some scenarios, we may expect that requests sent by clients do not go through the SSR service and directly request data sources.
|
379
|
-
|
380
|
-
:::info
|
381
|
-
Why the Data Loader is only executed server-side in SSR projects can be found in [FAQ](#faq)
|
382
|
-
|
356
|
+
:::tip
|
357
|
+
For more details on the `shouldRevalidate` function, refer to the [react-router](https://reactrouter.com/en/main/route/should-revalidate) documentation.
|
383
358
|
:::
|
384
359
|
|
385
|
-
|
386
|
-
|
387
|
-
1. During SSR degradation, it is not desired for the framework to send requests to the SSR service to obtain data. Instead, direct requests to the data source are preferred.
|
388
|
-
2. There is some cache on the client side, and we don't want to request SSR service to fetch data.
|
389
|
-
|
390
|
-
In these scenarios, we can use Client Loader. After adding the Client Loader, in the following scenarios, the code in the Client Loader will be called instead of sending requests to SSR services:
|
391
|
-
|
392
|
-
1. After SSR is downgraded to CSR, when fetch data on the client side, Client Loader will be executed instead of the framework sending requests to Data Loader (Server) to fetch data.
|
393
|
-
2. When performing SPA navigation in SSR projects and fetch data, Client Loader will be executed.
|
360
|
+
## Incorrect Usages
|
394
361
|
|
395
|
-
|
396
|
-
### Incorrect usage
|
397
|
-
|
398
|
-
1. The `loader` can only return serializable data. In the SSR environment, the return value of the `loader` function will be serialized as a JSON string and then deserialized into an object on the client side. Therefore, the `loader` function cannot return non-serializable data (such as functions).
|
362
|
+
1. The `loader` can only return serializable data. In an SSR environment, the return value of the `loader` function will be serialized as a JSON string and then deserialized as an object on the client side. Therefore, the `loader` function should not return non-serializable data such as functions.
|
399
363
|
|
400
364
|
:::warning
|
401
|
-
|
365
|
+
This limitation currently does not exist in CSR, but we strongly recommend adhering to it, as future versions may enforce this restriction in CSR as well.
|
402
366
|
:::
|
403
367
|
|
404
368
|
```ts
|
405
369
|
// This won't work!
|
406
|
-
export
|
407
|
-
|
408
|
-
|
370
|
+
export default () => {
|
371
|
+
return {
|
372
|
+
user: {},
|
373
|
+
method: () => {},
|
374
|
+
};
|
409
375
|
};
|
410
|
-
|
411
|
-
import { loader } from './page.data.ts';
|
412
|
-
export default function RouteComp() {
|
413
|
-
const data = loader();
|
414
|
-
}
|
415
376
|
```
|
416
377
|
|
417
|
-
2. Modern.js will call the `loader` function for you,
|
378
|
+
2. Modern.js will call the `loader` function for you, and you should not call it yourself:
|
418
379
|
|
419
|
-
```
|
380
|
+
```ts
|
420
381
|
// This won't work!
|
421
382
|
export const loader = async () => {
|
422
383
|
const res = fetch('https://api/user/profile');
|
@@ -429,7 +390,7 @@ export default function RouteComp() {
|
|
429
390
|
}
|
430
391
|
```
|
431
392
|
|
432
|
-
3.
|
393
|
+
3. Do not import `loader` files from route components, and do not import variables from route components into `loader` files. If you need to share types, use `import type`.
|
433
394
|
|
434
395
|
```ts
|
435
396
|
// Not allowed
|
@@ -445,85 +406,23 @@ export default function UserPage() {
|
|
445
406
|
}
|
446
407
|
|
447
408
|
// routes/layout.data.ts
|
448
|
-
import { fetch } from './layout.tsx'; // should not be imported from the
|
409
|
+
import { fetch } from './layout.tsx'; // should not be imported from the route component
|
449
410
|
export type ProfileData = {
|
450
411
|
/* some types */
|
451
412
|
};
|
452
413
|
|
453
|
-
export
|
414
|
+
export const loader = async (): Promise<ProfileData> => {
|
454
415
|
const res = await fetch('https://api/user/profile');
|
455
416
|
return await res.json();
|
456
417
|
};
|
457
418
|
```
|
458
419
|
|
459
|
-
4. When running on the server,
|
460
|
-
|
461
|
-
### FAQ
|
462
|
-
|
463
|
-
1. Relationship between `loader` and BFF functions
|
420
|
+
4. When running on the server, `loader` functions are packaged into a single bundle. Therefore, we do not recommend using `__filename` and `__dirname` in server code.
|
464
421
|
|
465
|
-
|
422
|
+
## Frequently Asked Questions
|
466
423
|
|
467
|
-
|
424
|
+
1. What is the relationship between `loader` and BFF functions?
|
468
425
|
|
469
|
-
|
426
|
+
In CSR projects, the `loader` executes on the client side and can directly call BFF functions to make API requests.
|
470
427
|
|
471
|
-
|
472
|
-
|
473
|
-
- **Simplified usage**, when using a server loader, the data fetching code for both the SSR and CSR phases is in the server loader (the real calls are made by the framework layer), and the code in the server loader doesn't need to care whether it's in a browser environment or a server-side environment.
|
474
|
-
- **Reducing data for network requests**, The server loader can be used as a BFF layer, which reduces the amount of data that needs to be fetched by the front-end at runtime.
|
475
|
-
- **Reduce client-side bundle size**, moving the logic code and its dependencies from the client to the server.
|
476
|
-
- **Improve maintainability**, moving the logic code to the server side reduces the direct impact of data logic on the front-end UI. In addition, the problem of mistakenly introducing server-side dependencies in the client-side bundle or client-side dependencies in the server-side bundle is also avoided.
|
477
|
-
|
478
|
-
|
479
|
-
## useLoader (old version)
|
480
|
-
|
481
|
-
**`useLoader`** is a legacy API in Modern.js v1. This API is a React Hook designed specifically for SSR applications, allowing developers to fetch data in components in isomorphic development.
|
482
|
-
|
483
|
-
:::tip
|
484
|
-
It is not necessary to use `useLoader` to fetch data in CSR projects.
|
485
|
-
:::
|
486
|
-
|
487
|
-
Here is the simplest example:
|
488
|
-
|
489
|
-
```tsx
|
490
|
-
import { useLoader } from '@modern-js/runtime';
|
491
|
-
|
492
|
-
export default () => {
|
493
|
-
const { data } = useLoader(async () => {
|
494
|
-
console.log('fetch in useLoader');
|
495
|
-
|
496
|
-
// No real request is sent here, just a hard coding data is returned.
|
497
|
-
// In a real project, the data obtained from the remote end should be returned.
|
498
|
-
return {
|
499
|
-
name: 'Modern.js',
|
500
|
-
};
|
501
|
-
});
|
502
|
-
|
503
|
-
return <div>Hello, {data?.name}</div>;
|
504
|
-
};
|
505
|
-
```
|
506
|
-
|
507
|
-
After running the above code, when you access the page, you can see that logs are output to the terminal, but not printed in the browser console.
|
508
|
-
|
509
|
-
This is because Modern.js collects the data returned by `useLoader` during server-side rendering and injects it into the corresponding HTML. If the SSR rendering is successful, you can see the following code snippet in the HTML:
|
510
|
-
|
511
|
-
```html
|
512
|
-
<script>
|
513
|
-
window._SSR_DATA = {};
|
514
|
-
</script>
|
515
|
-
```
|
516
|
-
|
517
|
-
This global variable is used to record data, and during the browser-side rendering process, this data is used first. If the data does not exist, the `useLoader` function will be executed again.
|
518
|
-
|
519
|
-
:::note
|
520
|
-
During the build phase, Modern.js will automatically generate a Loader ID for each `useLoader` and inject it into the SSR and CSR JS Bundles to associate the Loader with the data.
|
521
|
-
:::
|
522
|
-
|
523
|
-
Compared to `getServerSideProps` in Next.js, which fetches data before rendering, using `useLoader` allows you to get data required for local UI in the component without passing data through multiple layers. Similarly, you don't have to add redundant logic to the outermost data acquisition function because different routes require different data requests. Of course, `useLoader` also has some issues, such as difficulties in server-side code tree shaking and the need for an additional pre-rendering step on the server.
|
524
|
-
|
525
|
-
In the new version of Modern.js, a new Loader solution has been designed. The new solution solves these problems and can be optimized for page performance in conjunction with [conventional routing](/guides/basic-features/routes).
|
526
|
-
|
527
|
-
:::note
|
528
|
-
For detailed API information, see [useLoader](/apis/app/runtime/core/use-loader).
|
529
|
-
:::
|
428
|
+
In SSR projects, each `loader` is also a server-side API. We recommend using `loader` instead of BFF functions with an HTTP method of `get` to avoid an extra layer of forwarding and execution.
|