Adding a page and a menu item
Add a page
1. Create the view src/views/reports/sales.tsx (a client component):
'use client';
import PageHeader from '@/components/shell/PageHeader';
import { useI18n } from '@/hooks/useI18n';
import { usePageScript } from '@/hooks/usePageScript';
export default function ReportsSalesPage() {
const { t } = useI18n();
usePageScript();
return (
<>
<PageHeader title="reports.sales.title" subtitle="reports.sales.subtitle" section="nav.reports" sectionHref="/reports/sales" />
<div className="fd-page">
<section className="fd-card">
<div className="fd-card__body">
<p data-i18n="reports.sales.empty">{t('reports.sales.empty')}</p>
</div>
</section>
</div>
</>
);
}2. Create the route src/app/(app)/reports/sales/page.tsx. The folder (app) is the route group of the
sidebar shell (use (auth) or (blank) for the other layouts); the group name is not part of the URL:
import type { Metadata } from 'next';
import ReportsSalesPage from '@/views/reports/sales';
import { pageMetadata } from '@/core/titles';
export const metadata: Metadata = pageMetadata('/reports/sales');
export default function Page() {
return <ReportsSalesPage />;
}The route file stays a server component: it gives the static HTML its English title and renders the view.
3. Register the page in src/config/pages.ts:
{ path: '/reports/sales', id: 'reports-sales', title: 'reports.sales.title', layout: 'app' },| Field | Meaning |
|---|---|
path | URL of the page (relative to the base path) |
id | Page id: marks the current menu entry (the menu entry with the same id) |
title | Translation key of the document title (… · Orbix) |
layout | app, auth (plus variant) or blank: the route group of the page |
attrs | Optional attributes set on <html> while the page is shown |
4. Add the texts to every src/locales/<locale>/ folder, for example a new reports.json (see
Languages):
{
"sales": {
"title": "Sales report",
"subtitle": "Revenue by channel and region",
"empty": "No sales in this period yet."
}
}5. Optional page script. Code that works on the rendered markup (third-party widgets, charts, timers) goes in a
function passed to usePageScript(). It runs in the browser after render (never on the server); listeners on document/window added with listen(),
timers added with every() and charts are released when the page unmounts:
// src/scripts/reports.ts
import { listen } from '../core/scope';
export function init(): void {
listen(document, 'keydown', (event) => {
if (event.key === 'r' && event.altKey) document.querySelector<HTMLButtonElement>('[data-report-refresh]')?.click();
});
}import { init } from '@/scripts/reports';
// ...
usePageScript(init);For state that belongs to React (filters, lists, forms), use regular React state and hooks inside the component.
Visit http://localhost:3000/reports/sales. Next.js code-splits every route: each page loads its own JavaScript.
Pages that need real data fetch it from your API in the component (for example with
useEffector a data library such as TanStack Query) and render it with the same markup.
Add a menu item
The sidebar, the horizontal menu, the two-column rail and the command palette are all built from src/config/menu.ts.
Sections hold items and groups; groups hold children:
{
id: 'reports',
key: 'nav.reports', // section title (translation key)
icon: 'lc-chart-column',
items: [
{ id: 'reports-sales', to: '/reports/sales', key: 'nav.sales_report', icon: 'lc-chart-line' },
{
id: 'reports-more',
key: 'nav.more_reports',
icon: 'lc-folder-open',
children: [{ id: 'reports-stock', to: '/reports/stock', key: 'nav.stock_report' }],
},
],
},| Key | Meaning |
|---|---|
id | Unique navigation id; the page with the same id marks the entry as current and opens its group |
to | Route path of the page |
key | Translation key of the label |
icon | Icon id in the sprite: lc-* (Lucide), tb-* (Tabler), fd-i-* or ox-i-* (Orbix) |
badge | Optional counter: { count: 4, tone: 'primary' } |
Links
Use Next.js <Link href="/reports/sales"> (import Link from 'next/link') for links between pages: it adds the base
path and prefetches the page. Plain <a href> links created at runtime (for example inside a data grid cell) are
handled by the router too (src/components/shell/AppRoot.tsx), so the app never reloads. From TypeScript, call
navigateTo('/reports/sales') (src/core/navigate.ts).
Icons
Use any icon of the bundled sets with an SVG <use> reference:
<svg className="fd-icon" aria-hidden="true"><use href="#lc-chart-line" /></svg>Browse the sets on the Icons pages of the template. Only the icons you reference are included in the sprite:
after adding a new lc-* or tb-* icon, run npm run assets (it also runs with npm run dev, npm run build
and npm test).