Client-side Navigation
Because AppShell manages routing internally, it has its own instance of the RouterProvider from react-router.
To use client-side navigation from AppShell pages, instead of installing react-router to package.json and importing components and hooks from your application's dependencies, we must use the exports from @tailor-platform/app-shell, such as Link component, or useParams, useNavigate hooks. These components and hooks internally use the correct context for the AppShell RouterProvider to properly recognize the intent.
Exported React Router Hooks
AppShell re-exports the following hooks from react-router for use in your components:
useLocation- Access the current location objectuseNavigate- Programmatic navigationuseParams- Access route parametersuseSearchParams- Access and manipulate URL search parametersuseRouteError- Access error details in error boundariesLink- Client-side navigation component
Example Usage
import { useNavigate, useParams, useLocation, Link } 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>
{/* Client-side link */}
<Link to="/products">View Products</Link>
<button onClick={handleClick}>Go to Dashboard</button>
</div>
);
};Command Palette for Quick Navigation
AppShell includes a CommandPalette component that provides keyboard-driven quick navigation to any page in your application.
Features
- Keyboard Shortcut:
Cmd+K(Mac) orCtrl+K(Windows/Linux) - Fuzzy Search: Search by page title or path
- Hierarchical Display: Shows module > resource breadcrumbs
- Keyboard Navigation: Use arrow keys and Enter to navigate
- Multilingual: Supports English and Japanese locales
Setup
Add the CommandPalette component to your AppShell layout:
import { AppShell, SidebarLayout, CommandPalette } from "@tailor-platform/app-shell";
const App = () => (
<AppShell modules={modules} locale="en">
<>
<SidebarLayout />
<CommandPalette />
</>
</AppShell>
);The CommandPalette automatically:
- Collects all navigable routes from your module definitions
- Respects
accessControlsettings (hidden modules/resources won't appear) - Updates when navigation items change
- Adapts to the current locale
User Experience
- User presses
Cmd+K/Ctrl+Kanywhere in the app - Command palette dialog opens with fuzzy search
- User types to filter pages (e.g., "order detail")
- Navigate results with arrow keys
- Press Enter to navigate to selected page
No configuration needed - it just works!
Type-Safe Navigation with Generated Routes
When using file-based routing with the vite-plugin, you can enable automatic generation of type-safe route helpers. This provides compile-time checking for route paths and their parameters.
Setup
Enable generateTypedRoutes 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" },
}),
],
});This generates a src/routes.generated.ts file containing type definitions for all your routes.
Generated File
The generated file exports a paths helper with a type-safe for() method:
// 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
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>;
};Opt-In Design
This feature is opt-in. If you don't enable generateTypedRoutes, you can continue building paths dynamically:
// Still works without typed routes
navigate(`/orders/${orderId}`);HMR Support
The generated file is automatically regenerated when:
- A new
page.tsxis added - A
page.tsxis deleted - The dev server starts