Skip to content

Commit 22f027c

Browse files
committed
Merge branch 'main' into release-next
2 parents bc43707 + 39e4a69 commit 22f027c

File tree

19 files changed

+404
-391
lines changed

19 files changed

+404
-391
lines changed

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
.DS_Store
22
npm-debug.log
33

4-
/docs/api/
54
/website/build/
65
node_modules/
76

CHANGELOG.md

+177-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ function unstable_getContext() {
391391

392392
#### Middleware (unstable)
393393

394-
Middleware is implemented behind a `future.unstable_middleware` flag. To enable, you must enable the flag and the types in your `react-router-config.ts` file:
394+
Middleware is implemented behind a `future.unstable_middleware` flag. To enable, you must enable the flag and the types in your `react-router.config.ts` file:
395395

396396
```ts
397397
import type { Config } from "@react-router/dev/config";
@@ -716,6 +716,182 @@ In order to use your build-time loader data during pre-rendering, we now also ex
716716
- `@react-router/dev` - Add unstable support for splitting route modules in framework mode via `future.unstable_splitRouteModules` ([#11871](https://github.com/remix-run/react-router/pull/11871))
717717
- `@react-router/dev` - Add `future.unstable_viteEnvironmentApi` flag to enable experimental Vite Environment API support ([#12936](https://github.com/remix-run/react-router/pull/12936))
718718

719+
#### Split Route Modules (unstable)
720+
721+
> ⚠️ This feature is currently [unstable](https://reactrouter.com/community/api-development-strategy#unstable-flags), enabled by the `future.unstable_splitRouteModules` flag. We’d love any interested users to play with it locally and provide feedback, but we do not recommend using it in production yet.
722+
>
723+
> If you do choose to adopt this flag in production, please ensure you do sufficient testing against your production build to ensure that the optimization is working as expected.
724+
725+
One of the conveniences of the [Route Module API](https://reactrouter.com/start/framework/route-module) is that everything a route needs is in a single file. Unfortunately this comes with a performance cost in some cases when using the `clientLoader`, `clientAction`, and `HydrateFallback` APIs.
726+
727+
As a basic example, consider this route module:
728+
729+
```tsx filename=routes/example.tsx
730+
import { MassiveComponent } from "~/components";
731+
732+
export async function clientLoader() {
733+
return await fetch("https://example.com/api").then((response) =>
734+
response.json()
735+
);
736+
}
737+
738+
export default function Component({ loaderData }) {
739+
return <MassiveComponent data={loaderData} />;
740+
}
741+
```
742+
743+
In this example we have a minimal `clientLoader` export that makes a basic fetch call, whereas the default component export is much larger. This is a problem for performance because it means that if we want to navigate to this route client-side, the entire route module must be downloaded before the client loader can start running.
744+
745+
To visualize this as a timeline:
746+
747+
<docs-info>In the following timeline diagrams, different characters are used within the Route Module bars to denote the different Route Module APIs being exported.</docs-info>
748+
749+
```
750+
Get Route Module: |--=======|
751+
Run clientLoader: |-----|
752+
Render: |-|
753+
```
754+
755+
Instead, we want to optimize this to the following:
756+
757+
```
758+
Get clientLoader: |--|
759+
Get Component: |=======|
760+
Run clientLoader: |-----|
761+
Render: |-|
762+
```
763+
764+
To achieve this optimization, React Router will split the route module into multiple smaller modules during the production build process. In this case, we'll end up with two separate [virtual modules](https://vite.dev/guide/api-plugin#virtual-modules-convention) — one for the client loader and one for the component and its dependencies.
765+
766+
```tsx filename=routes/example.tsx?route-chunk=clientLoader
767+
export async function clientLoader() {
768+
return await fetch("https://example.com/api").then((response) =>
769+
response.json()
770+
);
771+
}
772+
```
773+
774+
```tsx filename=routes/example.tsx?route-chunk=main
775+
import { MassiveComponent } from "~/components";
776+
777+
export default function Component({ loaderData }) {
778+
return <MassiveComponent data={loaderData} />;
779+
}
780+
```
781+
782+
> 💡 This optimization is automatically applied in framework mode, but you can also implement it in library mode via `route.lazy` and authoring your route in multiple files as covered in our blog post on [lazy loading route modules.](https://remix.run/blog/lazy-loading-routes#advanced-usage-and-optimizations)
783+
784+
Now that these are available as separate modules, the client loader and the component can be downloaded in parallel. This means that the client loader can be executed as soon as it's ready without having to wait for the component.
785+
786+
This optimization is even more pronounced when more Route Module APIs are used. For example, when using `clientLoader`, `clientAction` and `HydrateFallback`, the timeline for a single route module during a client-side navigation might look like this:
787+
788+
```
789+
Get Route Module: |--~~++++=======|
790+
Run clientLoader: |-----|
791+
Render: |-|
792+
```
793+
794+
This would instead be optimized to the following:
795+
796+
```
797+
Get clientLoader: |--|
798+
Get clientAction: |~~|
799+
Get HydrateFallback: SKIPPED
800+
Get Component: |=======|
801+
Run clientLoader: |-----|
802+
Render: |-|
803+
```
804+
805+
Note that this optimization only works when the Route Module APIs being split don't share code within the same file. For example, the following route module can't be split:
806+
807+
```tsx filename=routes/example.tsx
808+
import { MassiveComponent } from "~/components";
809+
810+
const shared = () => console.log("hello");
811+
812+
export async function clientLoader() {
813+
shared();
814+
return await fetch("https://example.com/api").then((response) =>
815+
response.json()
816+
);
817+
}
818+
819+
export default function Component({ loaderData }) {
820+
shared();
821+
return <MassiveComponent data={loaderData} />;
822+
}
823+
```
824+
825+
This route will still work, but since both the client loader and the component depend on the `shared` function defined within the same file, it will be de-optimized into a single route module.
826+
827+
To avoid this, you can extract any code shared between exports into a separate file. For example:
828+
829+
```tsx filename=routes/example/shared.tsx
830+
export const shared = () => console.log("hello");
831+
```
832+
833+
You can then import this shared code in your route module without triggering the de-optimization:
834+
835+
```tsx filename=routes/example/route.tsx
836+
import { MassiveComponent } from "~/components";
837+
import { shared } from "./shared";
838+
839+
export async function clientLoader() {
840+
shared();
841+
return await fetch("https://example.com/api").then((response) =>
842+
response.json()
843+
);
844+
}
845+
846+
export default function Component({ loaderData }) {
847+
shared();
848+
return <MassiveComponent data={loaderData} />;
849+
}
850+
```
851+
852+
Since the shared code is in its own module, React Router is now able to split this route module into two separate virtual modules:
853+
854+
```tsx filename=routes/example/route.tsx?route-chunk=clientLoader
855+
import { shared } from "./shared";
856+
857+
export async function clientLoader() {
858+
shared();
859+
return await fetch("https://example.com/api").then((response) =>
860+
response.json()
861+
);
862+
}
863+
```
864+
865+
```tsx filename=routes/example/route.tsx?route-chunk=main
866+
import { MassiveComponent } from "~/components";
867+
import { shared } from "./shared";
868+
869+
export default function Component({ loaderData }) {
870+
shared();
871+
return <MassiveComponent data={loaderData} />;
872+
}
873+
```
874+
875+
If your project is particularly performance sensitive, you can set the `unstable_splitRouteModules` future flag to `"enforce"`:
876+
877+
```tsx filename=react-router-config.ts
878+
export default {
879+
future: {
880+
unstable_splitRouteModules: "enforce",
881+
},
882+
};
883+
```
884+
885+
This setting will raise an error if any route modules can't be split:
886+
887+
```
888+
Error splitting route module: routes/example/route.tsx
889+
890+
- clientLoader
891+
892+
This export could not be split into its own chunk because it shares code with other exports. You should extract any shared code into its own module and then import it within the route module.
893+
```
894+
719895
### Changes by Package
720896

721897
- [`create-react-router`](https://github.com/remix-run/react-router/blob/react-router%407.2.0/packages/create-react-router/CHANGELOG.md#720)

contributors.yml

+6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
- ayushmanchhabra
4343
- babafemij-k
4444
- barclayd
45+
- basan17
4546
- bavardage
4647
- bbrowning918
4748
- BDomzalski
@@ -75,6 +76,7 @@
7576
- dadamssg
7677
- damianstasik
7778
- danielberndt
79+
- danielweinmann
7880
- daniilguit
7981
- dauletbaev
8082
- david-bezero
@@ -158,6 +160,7 @@
158160
- JesusTheHun
159161
- jimniels
160162
- jmargeta
163+
- jmjpro
161164
- johnpangalos
162165
- jonkoops
163166
- jrakotoharisoa
@@ -173,6 +176,7 @@
173176
- kark
174177
- KAROTT7
175178
- kddnewton
179+
- ken0x0a
176180
- kentcdodds
177181
- kiliman
178182
- kkirsche
@@ -263,6 +267,7 @@
263267
- pwdcd
264268
- pyitphyoaung
265269
- refusado
270+
- renyu-io
266271
- reyronald
267272
- rifaidev
268273
- rimian
@@ -309,6 +314,7 @@
309314
- szhsin
310315
- tanayv
311316
- thecode00
317+
- theMosaad
312318
- theostavrides
313319
- thepedroferrari
314320
- thethmuu

0 commit comments

Comments
 (0)