Custom Routes

Add pages beyond the resource CRUD routes.

Not every page is a resource view. <CustomRoutes> lets you add arbitrary routes to the admin — a settings screen, a dashboard, a public marketing page — that render either inside the layout or standalone.

Adding routes

Drop <CustomRoutes> among your <Resource>s and give it <Route> children. Under the default react-router adapter these are react-router <Route>s:

src/App.tsx
import { Admin, CustomRoutes, Layout, Resource } from "@chelone/react";
import { Route } from "react-router";
import { AboutPage } from "./about";
import { dataProvider, queryClient } from "./providers";
import { PostList } from "./posts";
import { SettingsPage } from "./settings";
 
export function App() {
  return (
    <Admin dataProvider={dataProvider} queryClient={queryClient}>
      <Admin.Layout>
        <Layout />
      </Admin.Layout>
 
      <Resource name="posts" list={PostList} />
 
      {/* inside the layout — has the sidebar, app bar, and auth gate */}
      <CustomRoutes>
        <Route path="/settings" element={<SettingsPage />} />
      </CustomRoutes>
 
      {/* standalone — no layout, auth-agnostic */}
      <CustomRoutes noLayout>
        <Route path="/about" element={<AboutPage />} />
      </CustomRoutes>
    </Admin>
  );
}

Under another router adapter, use that adapter's own Route instead (see Admin).

Layout vs standalone

  • Default (<CustomRoutes>): the route renders inside <Layout> — it gets the sidebar, app bar, and (when requireAuth) the auth gate. Use it for admin pages that belong in the shell.
  • noLayout: the route renders standalone, outside the layout and auth-agnostic. Use it for login-adjacent or public pages that shouldn't show the admin chrome.

You can mix both — one <CustomRoutes> for in-shell pages, another with noLayout for public ones.

Linking to a custom route

Use the router adapter's navigation, useRedirect or useNavigate, so links work under whichever router adapter the admin runs on. A function passed to useRedirect returns the raw path, which gets the app basename prepended:

import { useRedirect } from "@chelone/react";
 
export function SettingsButton() {
  const redirect = useRedirect();
  return (
    <button type="button" onClick={() => redirect(() => "/settings")}>
      Settings
    </button>
  );
}

useNavigate takes the path directly: navigate("/settings").

Props

PropTypeDefaultDescription
childrenReactNode—The `<Route>` elements (or a fragment of them).
noLayoutboolean—When true these routes render STANDALONE (outside `<Layout>`, auth-agnostic — parity §0a-10): login-adjacent pages, public pages. When false/omitted they render inside `<Layout>` (can override resource paths by order).

Tips

  • noLayout routes render outside the auth gate — put anything that must stay public (a login page, a marketing page) here.
  • With-layout custom routes are matched before resource routes, wherever the <CustomRoutes> sits among the children, so a custom route overrides a resource path it matches exactly. What a broader pattern does depends on the adapter. react-router ranks by specificity first, so /:slug does not take over /posts. The TanStack adapter takes the first match in order, so there /:slug does take over /posts.