Page Entry

In this chapter, you will learn about the entry convention in Modern.js and how to customize it.

What is Entry?

Entry refers to the starting module of a page.

In a Modern.js project, each entry corresponds to an independent page and a server-side route. By default, Modern.js automatically determines the entry of a page based on directory conventions, but also supports customizing the entry 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 section.

Single Entry and Multiple Entries

The project initialized by Modern.js is a single entry project, with the following structure:

. ├── src │ └── routes │ ├── index.css │ ├── layout.tsx │ └── page.tsx ├── package.json ├── modern.config.ts └── tsconfig.json

In a Modern.js project, you can easily switch from single entry to multiple entries by running pnpm run new in the project directory and creating 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-entry

After running the command, Modern.js will automatically generate a new entry directory. At this point, 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.tsx

The 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}.

You can run pnpm run dev to start the development server. At this point, you will see a new route named /new-entry added, and the existing page routes remain unchanged.

NOTE

The concepts of single entry/multiple entry and SPA/MPA are not equivalent. The former pertains to how to configure and package the application, while the latter is about the patterns for organizing front-end applications. Each entry point can be either an SPA or a non-SPA.

Entry Types

Different entry types have different compilation and runtime behaviors.

By default, Modern.js scans the files under src/ before starting the project, identifies the entry, and generates the corresponding server-side route.

TIP
  • You can custom the recognition directory for page entries by using source.entriesDir.
  • If you need to customize the entry points, please refer to Custom Entries.

The entry directory must meet one of the following three conditions:

  1. Has a routes/ directory.
  2. Has an App.[jt]sx? file.
  3. Has an entry.[jt]sx? file.(Requires source.enableCustomEntry to be enabled)

When the src/ directory meets the conditions of an entry, Modern.js will consider the current application to be a single-entry application. Otherwise, Modern.js will scan the first-level directories under src/ and further determine if they are entries. In this case, the application is typically a multi-entry application.

TIP

In a single entry application, the default entry name is main.

Framework Mode Entry

Framework mode refers to using Modern.js's framework capabilities, such as convention routing, SSR (Server-Side Rendering), and integrated calls. Under this type of entry convention, the entries in the application are not the actual compilation entries. Modern.js will generate a wrapped entry during startup, which you can find in node_modules/.modern/[entryName]/index.js.

Conventional Routing

If there is a routes/ directory within the entry, we refer to this entry as a convention-based route. 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
│       └── page.tsx

In the above directory, the component exported in layout.tsx will be the outermost component, and the component exported in page.tsx will be the component of the / route.

For more information, please refer to Conventional Routing.

Self-controlled Routing

Manual Routing

If there is an App.[jt]sx? file within the entry, we refer to this entry as a self-controlled route. For example:

.
├── src
│   └── App.tsx

For entry points defined as src/App.tsx, Modern.js does not perform any additional routing operations. Developers can use the React Router 6 API for development, define client-side routes or not to set any client-side routes.

for example, define client-side routes in 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>
  );
};
NOTE

We recommend that developers use conventional routing. Modern.js provides a series of optimizations in resource loading and rendering for conventional routing by default and offers built-in SSR capabilities. When using manual routing, these capabilities need to be encapsulated by developers themselves.

Custom Entry

INFO

Using this features requires enabling source.enableCustomEntry.

By default, whether you use convention routing or self-controlled routing, Modern.js will automatically handle rendering. If you wish to customize this behavior, you can creating a custom entry file.

If there is an entry.[jt]sx file within the entry, Modern.js will no longer control the application's rendering process. You can call the createRoot and render functions within the entry.[jt]sx file to complete the entry logic for your application.

import { createRoot } from '@modern-js/runtime/react';
import { render } from '@modern-js/runtime/browser';

const ModernRoot = createRoot();

render(<ModernRoot />);

In the code above, the component returned by the createRoot function is either the component generated from the routes/ directory or the component exported by App.tsx.

The render function is used to handle rendering and mounting of the component. For example, if you want to execute some asynchronous tasks before rendering, you can achieve 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 />);
});

Build Mode Entry

INFO

Using this features requires enabling source.enableCustomEntry.

Build mode refers to the development mode that does not use Modern.js's runtime capabilities and only utilizes Modern.js's build capabilities. When the @modern-js/runtime dependency is not installed in the application, Modern.js will treat all entries as build mode entries.

In this case, if there is an entry.[jt]sx file within the entry, this file will be recognized as the build entry for webpack or Rspack. Modern.js will not automatically generate entry code at this time, and you need to mount the component to the DOM node yourself. For example:

src/entry.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

In build mode, the application will not be able to use Modern.js's runtime capabilities, such as:

  • Convention routing, the routing based on the files under src/routes

  • Server-Side Rendering (SSR)

  • The runtime configuration in the modern.config.js file will no longer take effect

Specify entry in the configuration file

In some cases, you may need to customize the entry configuration instead of using the entry conventions provided by Modern.js.

For example, if you want to migrate a non-Modern.js project to Modern.js and it is not structured according to Modern.js directory structure, there might be some migration costs involved in changing it to the conventional structure. In such cases, you can custom the entries.

Modern.js provides the following configuration options that you can set in modern.config.ts:

  • source.entries: Used to set custom entry objects.
  • source.disableDefaultEntries: Used to disable Modern.js's default entry scanning behavior. When you use custom entries, parts of your project structure might coincidentally match the Modern.js conventional directory structure, but you may not want Modern.js to generate entry configurations for them. Enabling this option can help avoid this issue.

Example

Here is an example of a custom entry point. You can also refer to the documentation of the corresponding configuration options for more usage.

modern.config.ts
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.

In-Depth

The concept of page entry is derived from the concept of Entrypoint in webpack. It is mainly used to configure the JavaScript or any other modules to be executed during the application startup. Webpack usually corresponds each entry to an HTML file in the output. The modules imported by the entry will be bundled and split into multiple chunks in the output. For a JavaScript module, it might be compiled into several chunks like dist/static/js/index.ea39u8.js.

Here's a summary of the differences between the concepts of entry and route:

  • Entry:Contains multiple modules to be executed during application startup.
  • Client Router:In Modern.js, it is usually implemented by react-router, determining which React component to load and render based on the browser's current URL using the History API.
  • Server Router:The server can mimic the behavior of devServer, replacing all 404 responses with the index.html page to implement client-side routing, or implement any routing logic as needed.

Their relationships are as follows:

  • Each webpack website project can contain multiple entries.
  • Each entry contains several modules (source files).
  • Each entry usually corresponds to an HTML file in the output.
  • Each HTML file can contain multiple client-side routing solutions (for example, using react-router and @tanstack/react-router in the same page).
  • Each HTML file can be mapped to multiple server-side routes.
  • Each HTML file can contain multiple client-side routing solutions, and when accessing different routes of a single-entry application, the same HTML file is actually used.

Troubleshooting

  1. Does each react-router defined client route generate a separate HTML file?

No. Each entry usually only generates one HTML file, and if a single entry contains multiple client routing systems, it will share the same HTML file.

  1. Does the convention routing project in the routes/ directory generate multiple HTML files?

No. Modern.js will scan the routes/ directory during startup and automatically generate client-side routes based on the file conventions. The HTML file generated corresponds to the routes/ directory.

  1. Does the Server Side Rendering (SSR) project generate multiple HTML files in the output?

A Server Side Rendering (SSR) project does not necessarily need to generate an HTML file in the output. It can only include the server-side JavaScript output. At this point, the react-router routing will be executed on the server side, and the HTML content will be rendered and responded while a request is triggered.

At the same time, Modern.js will also generate a complete client-side HTML file for each entry in the output, which can be used to fallback to client-side rendering when the SSR fails.

Another special case is a Single Entry Static Site Generation (SSG) project, even if it is built with a convention routing, Modern.js will also generate an HTML file for each page.tsx file.

Note that even if SSR is used, React still needs to go through the hydration phase, so the routing defined by react-router will still be executed on the client side.

  1. What are the exceptions to generating multiple HTML files?

You can configure html-rspack-plugin to generate multiple HTML files for each entry, or let multiple entries share the same HTML file.

  1. What is a Multi-Page Application (MPA)?

The "page" in a Multi-Page Application (MPA) refers to a static HTML file.

Generally, any application that outputs multiple entries and multiple HTML files can be called a Multi-Page Application.

Narrowly speaking, a Multi-Page Application does not contain client-side routing, and navigation between pages is usually achieved through elements like <a> tags. But in practice, Multi-Page Applications also often need to configure client-side routing to meet different needs.

Conversely, a application with react-router defined routes that generates only one HTML file is called a Single-Page Application (SPA).

Deprecated

Currently, if the entry directory meets the following conditions, it will also be considered an application entry:

  1. Has an index.[jt]sx? file.
  2. Has a pages/ directory.

The pages/ directory was the convention routing in older versions of Modern.js. Now, it is recommended to use the routes/ directory.

The index.[jt]sx? file supported Custom Bootstrap and Build Mode Entry in older versions. Now, it is recommended to use entry.[jt]sx? instead.

Custom Bootstrap

When there is an index.[jt]sx file in the entry, and the file's default export is a function, Modern.js will pass the default bootstrap function as an argument and use the exported function to replace the default bootstrap.

This allows developers to customize mounting components to DOM or add custom behaviors before mounting. For example:

export default (App: React.ComponentType, bootstrap: () => void) => {
  // do something before bootstrap...
  initSomething().then(() => {
    bootstrap();
  });
};

Build Mode Entry

When an index.[jt]sx file exists in the entry directory and does not export a function via export default, this entry will also be considered a build mode entry.