Skip to Content
FrameworkRouting

Routing

Every directory under app/ is a URL segment; every page.tsx is a renderable page.

Pages

app/page.tsx → /

import { Typography } from "antd"; export default function HomePage() { return <Typography.Title>Welcome to Manh</Typography.Title>; }

app/orders/page.tsx → /orders

import { ProTable } from "@ant-design/pro-components"; import { useApiClient } from "app/api"; export default function OrdersPage() { const api = useApiClient(); return ( <ProTable rowKey="id" request={async (p) => { const { data, total } = await api.orders.list({ page: p.current, pageSize: p.pageSize }); return { data, total, success: true }; }} columns={[ { title: "ID", dataIndex: "id" }, { title: "Customer", dataIndex: "customerName" }, { title: "Total", dataIndex: "total", valueType: "money" }, ]} /> ); }

Layouts

Layouts wrap all pages in their directory scope and nest automatically: parent layout → child layout → page.

app/layout.tsx — root layout

import { Outlet } from "react-router-dom"; import { ProLayout } from "@ant-design/pro-components"; export default function RootLayout() { return ( <ProLayout title="Manh Admin"> <Outlet /> </ProLayout> ); }

app/orders/layout.tsx — nested layout

import { Outlet } from "react-router-dom"; import { PageContainer } from "@ant-design/pro-components"; export default function OrdersLayout() { return ( <PageContainer title="Orders"> <Outlet /> </PageContainer> ); }

Every layout.tsx must render <Outlet />, otherwise child pages will not appear.

Dynamic Routes

Wrap a segment in brackets: app/orders/[id]/page.tsx → /orders/:id. Append :type for auto-validation ([id:uuid], [slug:slug], [n:number]) — invalid URLs render 404 before the page mounts.

import { useParams } from "react-router-dom"; import { Descriptions } from "antd"; import { useRequest } from "app/hooks"; import { useApiClient } from "app/api"; export default function OrderDetailPage() { const { id } = useParams(); const api = useApiClient(); const { data } = useRequest(() => api.orders.detail(id!), { refreshDeps: [id] }); return ( <Descriptions title={`Order ${id}`} bordered> <Descriptions.Item label="Customer">{data?.customerName}</Descriptions.Item> <Descriptions.Item label="Total">{data?.total}</Descriptions.Item> </Descriptions> ); }

Route Groups

Parentheses around a folder name create a route group — a segment that organizes files without appearing in the URL.

app/(admin)/users/page.tsx → /users (NOT /admin/users)

Use route groups to share a layout or middleware among unrelated routes.

Export a static meta object from a page or the root layout. The deploy writes it into that route’s HTML <head>, so search engines and link previews see the title, description and image without running the app.

// app/layout.tsx export const meta = { siteName: "Spa Hoa Sen", description: "Spa thảo dược ở Huế", lang: "vi" }; // app/pricing/page.tsx export const meta = { title: "Bảng giá", // → "Bảng giá · Spa Hoa Sen" description: "Giá gội, massage, liệu trình", image: "https://storage.manh.run/…/public/pricing.jpg", }; // app/admin/page.tsx export const meta = { noindex: true };

Values must be plain strings or true/false — the deploy reads the source, it never runs it. image must be an absolute https:// URL (upload under public/ with app/std/blob). Pages with URL params use the home page’s head for now.

Last updated on