docs/docs/en/plugin-development/client/router.md
In NocoBase, plugins register pages through routes. Two common approaches:
this.router.add() -- Register regular page routesthis.pluginSettingsManager.addMenuItem() + addPageTabItem() -- Register plugin settings pagesRoute registration is typically done in the plugin's load() method. See Plugin for details.
:::warning Note
For NocoBase v2 plugins, registered routes automatically get a /v prefix. You need to include this prefix when accessing the routes.
:::
NocoBase has the following default routes registered:
| Name | Path | Component | Description |
|---|---|---|---|
| admin | /v/admin/* | AdminLayout | Admin pages |
| admin.page | /v/admin/:name | AdminDynamicPage | Dynamically created pages |
| admin.settings | /v/admin/settings/* | AdminSettingsLayout | Plugin settings pages |
Register page routes via this.router.add(). Page components should use componentLoader for lazy loading, so page code is only loaded when actually visited.
:::warning Note
Page files must use export default to export the component.
:::
// pages/HelloPage.tsx
export default function HelloPage() {
return <h1>Hello, NocoBase!</h1>;
}
Register in the plugin's load():
import { Plugin } from '@nocobase/client-v2';
class MyPlugin extends Plugin {
async load() {
this.router.add('hello', {
path: '/hello',
// Lazy loading: the module is loaded only when /v/hello is visited
componentLoader: () => import('./pages/HelloPage'),
});
}
}
The first argument of router.add() is the route name, which supports dot notation . to express parent-child relationships. For example, root.home represents a child route of root.
In components, you can navigate to a route via ctx.router.navigate('/hello').
import { useFlowContext } from '@nocobase/flow-engine';
import { Button } from 'antd';
export default function SomeComponent() {
const ctx = useFlowContext();
return (
<Button onClick={() => ctx.router.navigate('/hello')}>
Go to Hello Page
</Button>
);
}
For more details, see the routing section in Component.
Implement nesting through dot notation. Parent routes use <Outlet /> to render child route content:
import { Outlet } from 'react-router-dom';
class MyPlugin extends Plugin {
async load() {
// Parent route, using element for inline layout
this.router.add('root', {
element: (
<div>
<nav>Navigation Bar</nav>
<Outlet />
</div>
),
});
// Child route, using componentLoader for lazy loading
this.router.add('root.home', {
path: '/', // -> /v/
componentLoader: () => import('./pages/HomePage'),
});
this.router.add('root.about', {
path: '/about', // -> /v/about
componentLoader: () => import('./pages/AboutPage'),
});
}
}
Route paths support dynamic parameters:
this.router.add('root.user', {
path: '/user/:id', // -> /v/user/:id
componentLoader: () => import('./pages/UserPage'),
});
In components, you can get dynamic parameters via ctx.route.params:
import { useFlowContext } from '@nocobase/flow-engine';
export default function UserPage() {
const ctx = useFlowContext();
const { id } = ctx.route.params; // Get dynamic parameter id
return <h1>User ID: {id}</h1>;
}
For more details, see the routing section in Component.
componentLoader (recommended): Lazy loading, suitable for page components. Page files need export default.element: Pass JSX directly, suitable for layout components or very lightweight inline pages.If the page itself has heavy dependencies, prefer componentLoader.
Register plugin settings pages via this.pluginSettingsManager. Registration has two steps -- first use addMenuItem() to register the menu entry, then use addPageTabItem() to register the actual page. Settings pages appear in the NocoBase "Plugin Settings" menu.
import { Plugin, Application } from '@nocobase/client-v2';
export class HelloPlugin extends Plugin<any, Application> {
async load() {
// Register menu entry
this.pluginSettingsManager.addMenuItem({
key: 'hello',
title: this.t('Hello Settings'),
icon: 'ApiOutlined', // Ant Design icon name, see https://5x.ant.design/components/icon
});
// Register page (key 'index' maps to the menu root path)
this.pluginSettingsManager.addPageTabItem({
menuKey: 'hello',
key: 'index',
title: this.t('Hello Settings'),
componentLoader: () => import('./settings/HelloSettingPage'),
});
}
}
After registration, the access path is /v/admin/settings/hello. When there is only one page under the menu, the top tab bar is automatically hidden.
If the settings page needs multiple sub-pages, register multiple addPageTabItem calls with the same menuKey -- tabs will appear automatically at the top:
import { Plugin, Application } from '@nocobase/client-v2';
class HelloPlugin extends Plugin<any, Application> {
async load() {
// Register menu entry
this.pluginSettingsManager.addMenuItem({
key: 'hello',
title: this.t('HelloWorld'),
icon: 'ApiOutlined',
});
// Tab 1: General settings (key 'index' maps to /v/admin/settings/hello)
this.pluginSettingsManager.addPageTabItem({
menuKey: 'hello',
key: 'index',
title: this.t('General'),
componentLoader: () => import('./settings/GeneralPage'),
});
// Tab 2: Advanced settings (maps to /v/admin/settings/hello/advanced)
this.pluginSettingsManager.addPageTabItem({
menuKey: 'hello',
key: 'advanced',
title: this.t('Advanced'),
componentLoader: () => import('./settings/AdvancedPage'),
});
}
}
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique menu identifier, must not contain . |
title | ReactNode | No | Menu title |
icon | string | ReactNode | No | Menu icon, rendered as built-in Icon when string |
sort | number | No | Sort value, smaller values appear first, default 0 |
showTabs | boolean | No | Whether to show top tab bar, auto-determined by page count by default |
hidden | boolean | No | Whether to hide the navigation entry |
| Field | Type | Required | Description |
|---|---|---|---|
menuKey | string | Yes | The key of the parent menu, corresponding to addMenuItem's key |
key | string | Yes | Unique page identifier. 'index' means the default page, mapped to the menu root path |
title | ReactNode | No | Page title (displayed on the tab) |
componentLoader | Function | No | Lazy-load page component (recommended) |
Component | Component | No | Pass component directly (choose one of componentLoader or this) |
sort | number | No | Sort value, smaller values appear first |
hidden | boolean | No | Whether to hide in the tab bar |
link | string | No | External link; when set, clicking the tab navigates to the external URL |
load()