AppShell manages routing internally using React Router v7. To ensure proper functionality, you must use AppShell’s re-exported navigation components and hooks instead of installing React Router directly.
Why use AppShell’s exports?
AppShell has its own RouterProvider instance. Using components and hooks from @tailor-platform/app-shell ensures they connect to the correct router context.
Do not install react-router directly or import from it. Always use AppShell’s re-exports to avoid context mismatches.
Navigation hooks
AppShell re-exports these essential React Router hooks:
Hook Purpose useLocationAccess the current location object useNavigateProgrammatic navigation useParamsAccess route parameters useSearchParamsAccess and manipulate URL search parameters useRouteErrorAccess error details in error boundaries
Navigation components
Component Purpose LinkClient-side navigation component
Basic usage
Programmatic navigation
import { useNavigate , useParams , useLocation } from "@tailor-platform/app-shell" ;
const MyComponent = () => {
const navigate = useNavigate ();
const { id } = useParams ();
const location = useLocation ();
const handleClick = () => {
// Navigate programmatically
navigate ( "/dashboard/overview" );
};
return (
< div >
< p > Current path: { location . pathname } </ p >
< p > Route param ID: { id } </ p >
< button onClick = { handleClick } > Go to Dashboard </ button >
</ div >
);
};
Link component
import { Link } from "@tailor-platform/app-shell" ;
const Navigation = () => {
return (
< nav >
< Link to = "/products" > View Products </ Link >
< Link to = "/orders/123" > Order #123 </ Link >
</ nav >
);
};
Search parameters
Manipulate query strings with useSearchParams:
import { useSearchParams } from "@tailor-platform/app-shell" ;
const ProductList = () => {
const [ searchParams , setSearchParams ] = useSearchParams ();
const category = searchParams . get ( "category" );
const page = searchParams . get ( "page" ) || "1" ;
const updateCategory = ( newCategory : string ) => {
setSearchParams ({ category: newCategory , page: "1" });
};
return (
< div >
< p > Current category: { category } </ p >
< p > Page: { page } </ p >
< button onClick = { () => updateCategory ( "electronics" ) } > Electronics </ button >
</ div >
);
};
Command palette
The CommandPalette component provides keyboard-driven quick navigation to any page in your application.
Features
Keyboard shortcut : Cmd+K (Mac) or Ctrl+K (Windows/Linux)
Fuzzy search : Find pages by title or path
Hierarchical display : Shows module > resource breadcrumbs
Keyboard navigation : Arrow keys and Enter to navigate
Multilingual : Supports English and Japanese locales
Smart filtering : Respects access control (hidden routes don’t appear)
Setup
Add the CommandPalette to your AppShell layout:
import {
AppShell ,
SidebarLayout ,
CommandPalette
} from "@tailor-platform/app-shell" ;
const App = () => (
< AppShell modules = { modules } locale = "en" >
<>
< SidebarLayout />
< CommandPalette />
</>
</ AppShell >
);
How it works
The CommandPalette automatically:
Collects all navigable routes from your module definitions
Respects guard functions (routes with hidden() won’t appear)
Updates when navigation items change
Adapts to the current locale
No additional configuration needed—just add the component and it works!
User experience
Open command palette
User presses Cmd+K or Ctrl+K anywhere in the app
Search for pages
Dialog opens with fuzzy search. Type to filter (e.g., “order detail”)
Navigate results
Use arrow keys to navigate through results
Navigate to page
Press Enter to navigate to the selected page
Type-safe navigation
When using file-based routing with the Vite plugin, you can enable automatic generation of type-safe route helpers.
Enable typed routes
Configure in your Vite config:
// vite.config.ts
import { defineConfig } from "vite" ;
import react from "@vitejs/plugin-react" ;
import { appShellRoutes } from "@tailor-platform/app-shell-vite-plugin" ;
export default defineConfig ({
plugins: [
react (),
appShellRoutes ({
pagesDir: "src/pages" ,
// Enable with default output path ("src/routes.generated.ts")
generateTypedRoutes: true ,
// Or customize output path:
// generateTypedRoutes: { output: "src/my-routes.ts" },
}),
] ,
}) ;
Generated file
The plugin generates src/routes.generated.ts:
// src/routes.generated.ts (auto-generated)
import { createTypedPaths } from "@tailor-platform/app-shell" ;
type RouteParams = {
"/" : {};
"/dashboard" : {};
"/orders" : {};
"/orders/:id" : { id : string };
"/orders/:orderId/items/:itemId" : { orderId : string ; itemId : string };
};
export const paths = createTypedPaths < RouteParams >();
export type { RouteParams };
Usage with TypeScript
import { useNavigate } from "@tailor-platform/app-shell" ;
import { paths } from "./routes.generated" ;
const MyComponent = () => {
const navigate = useNavigate ();
// ✅ Static route - no params needed
const goToDashboard = () => {
navigate ( paths . for ( "/dashboard" ));
};
// ✅ Dynamic route - params required and type-checked
const goToOrder = ( orderId : string ) => {
navigate ( paths . for ( "/orders/:id" , { id: orderId }));
};
// ✅ Multiple params
const goToOrderItem = ( orderId : string , itemId : string ) => {
navigate ( paths . for ( "/orders/:orderId/items/:itemId" , { orderId , itemId }));
};
// ✅ Query string passthrough
const goToOrderWithTab = ( orderId : string ) => {
navigate ( paths . for ( "/orders/:id?tab=details" , { id: orderId }));
};
// ✅ Dynamic query values via template literal
const goToOrderWithDynamicQuery = ( orderId : string , tab : string ) => {
navigate ( paths . for ( `/orders/:id?tab= ${ tab } ` , { id: orderId }));
};
// ❌ TypeScript error: missing required params
// navigate(paths.for("/orders/:id"));
// ❌ TypeScript error: invalid path
// navigate(paths.for("/invalid/path"));
return < button onClick = { goToDashboard } > Go to Dashboard </ button > ;
};
Benefits
Compile-time safety TypeScript catches invalid routes and missing parameters before runtime
Auto-completion IDE autocomplete for all routes and their required parameters
Refactoring support Rename routes and parameters with confidence
Opt-in design Use only when needed—manual path strings still work
Opt-in design
Typed routes are completely optional. Without generateTypedRoutes, you can continue building paths dynamically:
// Still works without typed routes
navigate ( `/orders/ ${ orderId } ` );
HMR support
The generated file automatically updates when:
A new page.tsx is added
A page.tsx is deleted
The dev server starts
The file regenerates automatically during development, so your types always stay in sync with your routes.
File-based routing Define routes using directory structure
Modules and resources Understand AppShell’s routing architecture