Modern.js's routing is based on React Router 6 and provides multiple types of routing modes. According to different entry types, routing is divided into three modes: Conventional Routing, Self-controlled Routing, and Other.
The routing mentioned in this section refers to client-side routing, i.e., SPA routing.
With routes/
as the convention for entry points, Modern.js automatically generates the corresponding routing structure based on the file system.
Modern.js supports the popular conventional routing mode in the industry: Nested Routing. When using nested routing, the page's routing corresponds to the UI structure, and we will introduce this routing mode in detail.
Under the routes/
directory, the directory name is mapped to the route URL. Modern.js has two file conventions, layout.[jt]sx
and page.[jt]sx
(abbreviated as.tsx
). These two files determine the layout structure of the application. layout.tsx
is used as the layout component, and page.tsx
acts as the content component, which is the leaf node of the entire route (a route has only one leaf node and must end with a leaf node).
For example, the following directory structure:
This will generate the following two routes:
/
/user
When layout.tsx
is added, assuming the following directory:
Here, routes/layout.tsx
will be used as the layout component for all components under the /
route, and routes/user/layout.tsx
will be used as the layout component for all route components under the /user
route.
When the route is /
, the following UI layout will be displayed:
Similarly, routes/user/layout.tsx
will be used as the layout component for all components under the /user
route. When the route is /user
, the following UI layout will be displayed:
The <Layout>
component refers to all layout.tsx
files under the routes/
directory. They represent the layout of the corresponding route segment and use <Outlet>
to represent child components.
<Outlet>
is a new API in React Router 6. For more details, please refer to Outlet.
To simplify the introduction of the relationship between <Layout>
and <Outlet>
, the following file directory is used as an example:
/
, the <Outlet>
in routes/layout.tsx
represents the component exported in routes/page.tsx
, generating the following UI structure:/blog
, the <Outlet>
in routes/layout.tsx
represents the component exported in routes/blog/page.tsx
, generating the following UI structure:/user
, the <Outlet>
in routes/layout.tsx
represents the component exported in routes/user/layout.tsx
. The <Outlet>
in routes/user/layout.tsx
represents the component exported in routes/user/page.tsx
, generating the following UI structure:In summary, if there is a layout.tsx
file under the sub-route's file directory, the <Outlet>
in the parent layout.tsx
will represent the layout.tsx
in the sub-route file directory. Otherwise, it will represent the page.tsx
in the sub-route file directory.
All routes should end with the <Page>
component. If the developer introduces the <Outlet>
component in the page.tsx
file, there will be no effect.
Each Layout
,$
or Page
file can define its own config
file, such as page.config.ts
. In this file, we have an conventinal on a named export called handle
, which you can define any properties:
These properties as defined are available via the useMatches
hook:
Routes generated from file directories named with []
will be handled as dynamic routes. For example, the following file directory:
The routes/[id]/page.tsx
file will be converted to the /:id
route. Except for the exact matching /blog
route, all other /xxx
routes will match this route.
In the component, you can use useParams to get the corresponding named parameter.
In the loader, params will be passed as the input parameter of the loader function, and you can get the parameter value through params.xxx
.
Routes generated from file directories named with [$]
will be treated as dynamic optional routes. For example, the following file directory:
The routes/user/[id$]/page.tsx
file will be converted to the /user/:id?
route. All routes under /user
will match this route, and the id
parameter is optional. This route is usually used to distinguish between creation and editing.
In the component, you can use useParams to get the corresponding named parameter.
In the loader, params will be passed as the input parameter of the loader function, and you can get the parameter value through params.xxx
.
If you create a $.tsx
file under the routes directory, it will be treated as the catch-all routing component. When there is no matching route, this component will be rendered.
$.tsx
can be considered as a special page
route component. When there is a layout.tsx
file in the current directory, $.tsx
will be rendered as a child component of layout
.
For example, the following directory structure:
When accessing any path that does not match(For example /blog/a
), the routes/$.tsx
component will be rendered, because there is layout.tsx
here, the rendered UI is as follows.
If you want access to /blog
to also match the blog/$.tsx
file, you need to delete the blog/layout.tsx
file in the same directory and make sure that there are no other subroutes under blog
.
As same, you can use useParams in $.tsx
to capture the remaining parts of the URL.
When the directory name starts with __
, the directory name will not be converted to an actual route path. For example, the following file directory:
Modern.js will generate two routes, /login
and /signup
. The __auth/layout.tsx
component will serve as the layout component for login/page.tsx
and signup/page.tsx
, but __auth
will not be a route path segment.
This feature is very useful when you need to create independent layouts for certain types of routes or want to classify routes.
In some cases, the project requires complex routing, but these routes do not have independent UI layouts. If you create routes like ordinary file directories, it will result in deep directory levels.
Therefore, Modern.js supports using .
to separate route segments instead of file directories. For example, when you need /user/profile/2022/edit
, you can directly create the following files:
When accessing the route, you will get the following UI layout:
In each directory under routes/
, developers can create a loading.tsx
file that exports a <Loading>
component by default.
When this component and the layout
component exist in the route directory, the <Loading>
component will be used as the fallback UI when switching routes in this sub-route. For example, the following file directory:
When defining loading.tsx
, it is equivalent to the following layout:
When the route is /
:
When the route is /blog
:
When the route is /blog/123
:
When the Layout component does not exist in the directory, the Loading component in that directory will not take effect. Modern.js recommends having a root Layout and root Loading.
When the route jumps from /
to /blog
, if the JS Chunk of the blog/page
component has not been loaded yet, the UI of the component exported in loading.tsx
will be displayed first.
Similarly, when the route jumps from /
or /blog
to /blog/123
, if the JS Chunk of the blog/[id]/page
component has not been loaded yet, the UI of the component exported in loading.tsx
will be displayed first.
You can use a Data Loader file to redirect a route. For example, if you have a routes/user/page.tsx
file and want to redirect the corresponding route, you can create a routes/user/page.data.ts
file:
If you want to redirect in a component,you can navigate by useNavigate
hook, for example:
In each directory under routes/
, developers can also define an error.tsx
file that exports an <ErrorBoundary>
component by default.
When this component exists in the routes directory, any rendering errors will be caught by the ErrorBoundary
component. When the layout.tsx
file is not defined in the directory, the <ErrorBoundary>
component will not take effect.
<ErrorBoundary>
can return the UI view when an error occurs. When the <ErrorBoundary>
component is not declared in the current level, the error will bubble up to a higher-level component until it is caught or thrown. At the same time, when a component has an error, it will only affect the route component and its child components that catch the error. The status and view of other components are not affected and can continue to interact.
In the <ErrorBoundary>
component, you can use useRouteError to get specific information about the error:
In each root Layout
component (routes/layout.ts
), you can dynamically define runtime configuration:
In some scenarios, you may need to perform some operations before rendering the application. You can define an init
hook in routes/layout.tsx
. The init
hook will be executed on both the client and server side. A basic example of usage is as follows:
By using the init
hook, you can mount some global data, and the runtimeContext
variable can be accessed in other parts of the application:
This feature is very useful when the application needs pre-rendered data, custom data injection, or framework migration (such as Next.js).
When used with the SSR feature, the data returned by the init
hook during SSR can be obtained on the client side. Developers can decide whether to re-fetch data on the client side to overwrite the SSR data. For example:
In conventional routing, Modern.js automatically splits routes into chunks based on the route. When a user visits a specific route, the corresponding chunk will be loaded automatically, effectively reducing the loading time of the initial screen.
However, this also brings a problem: if the chunk corresponding to the route has not finished loading when the user visits the route, a white screen will appear.
In this case, you can define a Loading
component to display a custom Loading
component before the static assets are loaded.
To further improve the user experience and reduce loading time, Modern.js supports defining the prefetch
attribute on the Link component to preload static assets and data.
The prefetch
attribute has three optional values:
none
: default value, no prefetching, no additional behavior.intent
: This is the value we recommend for most scenarios. When you hover over the Link, the corresponding chunk and data defined in the data loader will be loaded automatically. When you move the mouse away, the loading will be cancelled automatically. In our tests, even fast clicks can reduce loading time by about 200ms.render
: The corresponding chunk and data defined in the Data Loader will be loaded when the Link component is rendered.render
and not splitting static assets based on the route?render
, you can specify which routes to load during the initial screen, and you can control the rendering so that only when the Link
component enters the visible area, the Link
component will be rendered.render
, static assets will only be loaded when the system is idle, and will not compete with the static assets of the initial screen for network assets.With src/App.tsx
as the convention for entry points, Modern.js will not perform any additional routing operations. Developers can use the API of React Router 6 for development, for example:
Modern.js provides a series of optimizations for resource loading and rendering for conventional routing by default, and provides out-of-the-box SSR capabilities. When using self-controlled routing, developers need to encapsulate these capabilities themselves. It is recommended to use conventional routing.
By default, Modern.js will enable the built-in routing scheme, which is React Router.
As mentioned above, when the runtime.router
configuration is enabled, Modern.js will export the API of React Router from the @modern-js/runtime/router
namespace for developers to use, ensuring that developers and Modern.js are using the same code, and automatically wrapping the Provider
component according to the router configuration. In addition, in this case, the code of React Router will be packed into the JS output.
If the project already has its own routing plan or does not need to use client-side routing, this feature can be disabled.
As mentioned above, if the runtime.router
configuration is disabled and react-router-dom
is used directly for project routing management, the Provider
needs to be wrapped according to the React Router documentation.