Skip to main content
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.
AppShell re-exports these essential React Router hooks:
HookPurpose
useLocationAccess the current location object
useNavigateProgrammatic navigation
useParamsAccess route parameters
useSearchParamsAccess and manipulate URL search parameters
useRouteErrorAccess error details in error boundaries
ComponentPurpose
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>
  );
};
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:
  1. Collects all navigable routes from your module definitions
  2. Respects guard functions (routes with hidden() won’t appear)
  3. Updates when navigation items change
  4. Adapts to the current locale
No additional configuration needed—just add the component and it works!

User experience

1

Open command palette

User presses Cmd+K or Ctrl+K anywhere in the app
2

Search for pages

Dialog opens with fuzzy search. Type to filter (e.g., “order detail”)
3

Navigate results

Use arrow keys to navigate through results
4

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

Build docs developers (and LLMs) love