Through this chapter, you can understand the entry conventions in Modern.js and how to customize entries.
Entry refers to the starting module of a page.
In a Modern.js application, each entry corresponds to an independent page and a server-side route. By default, Modern.js automatically determines page entries based on directory conventions, and also supports customizing entries through configuration options.
Many configuration options provided by Modern.js are divided by entry, such as page title, HTML template, page meta information, whether to enable SSR/SSG, server-side routing rules, etc. If you want to learn more about the technical details of entries, please refer to the In-Depth chapter.
The application initialized by Modern.js is a single-entry application with the following structure:
.
├── src
│ └── routes
│ ├── index.css
│ ├── layout.tsx
│ └── page.tsx
├── package.json
├── modern.config.ts
└── tsconfig.jsonIn a Modern.js application, you can easily switch from single entry to multiple entries. Execute pnpm run new in the application directory and follow the prompts to create an entry:
? Please select the operation you want: Create Element
? Please select the type of element to create: New "entry"
? Please fill in the entry name: new-entryAfter execution, Modern.js will automatically generate a new entry directory, and you can see that the src/ directory has the following structure:
.
├── myapp # Original entry
│ └── routes
│ ├── index.css
│ ├── layout.tsx
│ └── page.tsx
└── new-entry # New entry
└── routes
├── index.css
├── layout.tsx
└── page.tsxThe original entry code has been moved to a directory with the same name as the name field in package.json, and a new-entry entry directory has been created.
Modern.js will use the entry with the same name as the name field in package.json as the main entry. The route of the main entry is /, and the route of other entries is /{entryName}. For example, when the name in package.json is myapp, src/myapp will be the main entry of the application.
You can execute pnpm run dev to start the development server. At this time, you can see that a new route named /new-entry has been added, and the routes of the original pages have not changed.
The concepts of single entry/multiple entry and SPA/MPA are not equivalent. The former is about how to configure and package the application, while the latter is a pattern for organizing front-end applications. Each entry can be SPA or non-SPA.
Modern.js supports three entry types, each with different use cases and characteristics. Choosing the appropriate entry type can help you better organize your code.
Modern.js automatically scans directories to identify entries that meet the criteria. A directory is recognized as an entry if it meets one of the following three conditions:
routes/ directory → Convention routing entryApp.[jt]sx? file → Self-controlled routing entryentry.[jt]sx? file → Custom entrysrc/ 本身满足入口条件 → 单入口应用src/ 不满足条件 → 扫描 src/ 下的子目录 → 多入口应用indexYou can modify the directory for identifying entries through source.entriesDir.
Next, we will introduce the usage of each entry type in detail.
If there is a routes/ directory in the entry, we call this entry a convention routing entry. Modern.js will scan the files under routes/ during startup and automatically generate client-side routes (react-router) based on file conventions. For example:
src/
└── routes/
├── layout.tsx # Layout component (optional)
├── page.tsx # Homepage component (/ route)
├── about/
│ └── page.tsx # About page (/about route)
└── blog/
├── page.tsx # Blog list page (/blog route)
└── [id]/
└── page.tsx # Blog detail page (/blog/:id route)Component correspondence
| File | Route | Description |
|---|---|---|
routes/layout.tsx | Global layout | Outer container for all pages |
routes/page.tsx | / | Homepage |
routes/about/page.tsx | /about | About page |
routes/blog/[id]/page.tsx | /blog/:id | Dynamic route page |
For more details, please refer to Routing Solution.
If there is an App.[jt]sx? file in the entry, this entry is a self-controlled routing entry. This method gives developers complete routing control.
.
├── src
│ └── App.tsxFor the entry defined as src/App.tsx, Modern.js does not perform additional routing operations. Developers can use the React Router v7 API to set up client-side routes, or not set up client-side routes. For example, the following code sets up client-side routes in the application:
import { BrowserRouter, Route, Routes } from '@modern-js/runtime/router';
export default () => {
return (
<BrowserRouter>
<Routes>
<Route index element={<div>index</div>} />
<Route path="about" element={<div>about</div>} />
</Routes>
</BrowserRouter>
);
};We recommend developers use convention routing. Modern.js provides a series of optimizations in resource loading and rendering for convention routing by default and offers built-in SSR capabilities. When using self-controlled routing, these capabilities need to be encapsulated by developers themselves.
By default, when using convention routing or self-controlled routing, Modern.js will automatically handle rendering. If you want to customize this behavior, you can implement it through a custom entry file.
Custom entries can coexist with convention routing and self-controlled routing entries, customizing the application initialization logic.
If there is an entry.[jt]sx file in the entry, Modern.js will no longer control the application's rendering process. You can call the createRoot and render functions in the entry.[jt]sx file to complete the application entry logic.
import { createRoot } from '@modern-js/runtime/react';
import { render } from '@modern-js/runtime/browser';
// Create root component
const ModernRoot = createRoot();
// Render to DOM
render(<ModernRoot />);In the code above, the component returned by the createRoot function is the component generated from the routes/ directory or exported by App.tsx. The render function is used to handle rendering and mounting components. For example, if you want to execute some asynchronous tasks before rendering, you can implement it like this:
import { createRoot } from '@modern-js/runtime/react';
import { render } from '@modern-js/runtime/browser';
const ModernRoot = createRoot();
async function beforeRender() {
// some async request
}
beforeRender().then(() => {
render(<ModernRoot />);
});If you don't want to use any of Modern.js's runtime capabilities, you can also mount the component to the DOM node yourself, for example:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));In this mode, you will not be able to use Modern.js framework's runtime capabilities, such as:
src/routesIn some cases, you may need to customize the entry configuration instead of using the entry conventions provided by Modern.js.
For example, if you need to migrate a non-Modern.js application to Modern.js, and it is not structured according to Modern.js's directory structure, there might be some migration costs involved in changing it to the conventional structure. In such cases, you can use custom entries.
Modern.js provides the following configuration options that you can set in modern.config.ts:
Here is an example of a custom entry. You can also refer to the documentation of the corresponding configuration options for more usage.
export default defineConfig({
source: {
entries: {
// Specify an entry named 'my-entry'
'my-entry': {
// Path to the entry module
entry: './src/my-page/index.tsx',
// Disable automatic generation of entry code by Modern.js
disableMount: true,
},
},
// Disable entry scanning behavior
disableDefaultEntries: true,
},
});It is worth noting that, by default, Modern.js considers entries specified through the configuration as framework mode entries and will automatically generate the actual compilation entry.
If your application is migrating from build tools like Webpack or Vite to the Modern.js framework, you typically need to enable the disableMount option in the entry configuration. In this case, Modern.js will treat the entry as a build mode entry.
The concept of page entry is derived from the concept of Entrypoint in webpack. It is mainly used to configure JavaScript or other modules to be executed during application startup. webpack's best practice for web applications usually corresponds entries to HTML output files, meaning each additional entry will eventually generate a corresponding HTML file in the output. The modules imported by the entry will be compiled and packaged into multiple Chunk outputs. For example, JavaScript modules may ultimately generate several file outputs similar to dist/static/js/index.ea39u8.js.
It's important to distinguish the relationships between concepts such as entry and route:
react-router, using the History API to determine which React component to load and display based on the browser's current URL.Their corresponding relationships are as follows:
react-router and @tanstack/react-router in the same page)react-router generate a separate HTML file?No. Each entry usually only generates one HTML file. If multiple client routing systems are defined in a single entry, they will share this one HTML file.
page.tsx file in the routes/ directory of convention routing generate an HTML file?No. Convention routing is a client-side routing solution implemented based on react-router. Its convention is that each page.tsx file under the routes/ directory corresponds to a client-side route of react-router. routes/ itself serves as a page entry, corresponding to one HTML file in the final output.
When using server-side rendering applications, it is not necessary to generate an HTML output at build time. It can only include server-side JavaScript output for rendering. At this time, react-router will run and schedule routes on the server side, rendering and responding with HTML content on each request.
However, Modern.js will still generate a complete client-side output containing HTML files for each entry at build time, which can be used to downgrade to client-side rendering when server-side rendering fails.
Another special case is a project using Static Site Generation (SSG). Even if it is a single-entry SSG application built with convention routing, Modern.js will generate a separate HTML file for each page.tsx file outside the webpack process.
It should be noted that even when server-side rendering is enabled, React usually still needs to execute the hydration phase and run react-router routing on the frontend.
You can configure html-rspack-plugin to generate multiple HTML outputs for each entry, or have multiple entries share one HTML output.
The "page" in a Multi-Page Application refers to a static HTML file.
Generally, any web application that contains multiple entries and multiple HTML file outputs can be called a multi-page application.
In a narrow sense, a multi-page application may not contain client-side routing and only navigates between static HTML pages through elements like <a> tags. However, in practice, multi-page applications often need to configure client-side routing for their entries to meet different needs.
Conversely, a single-entry application that defines multiple routes through react-router is called a Single Page Application because it only generates one HTML file output.