Skip to main content
Creates a top-level module that appears in the main navigation. Modules contain resources and can optionally have their own landing page.

Signature

function defineModule(props: DefineModuleProps): Module

Parameters

path
string
required
URL path for the module.
component
(props: ResourceComponentProps) => ReactNode
Landing page component. Optional - if omitted, module redirects to first resource.Receives ResourceComponentProps:
  • title: string - Title of the resource
  • icon?: ReactNode - Optional icon
  • resources?: Resource[] - Optional sub-resources
resources
Resource[]
required
Array of resources within this module. Define using defineResource().
meta
object
Metadata configuration for the module.
meta.title
string | LocalizedString
Display title in navigation. Defaults to path in capital case.
meta.icon
ReactNode
Icon displayed in navigation.
meta.breadcrumbTitle
string | ((segment: string) => string)
Custom breadcrumb segment title for this page.
guards
Guard[]
Array of guard functions to control access. Guards are executed in order. If any guard returns non-pass result, access is denied.See Route Guards for details.
errorBoundary
ReactNode
Error boundary component for this module and its child resources. When an error occurs in this module or its resources, this component will render.Use the useRouteError hook to access error details within the component.

Return Type

Module
Module
A module object that can be passed to the modules prop of AppShell.

Examples

Basic Module with Component

import { defineModule, defineResource } from "@tailor-platform/app-shell";
import { Package } from "lucide-react";

const productModule = defineModule({
  path: "products",
  meta: {
    title: "Product Management",
    icon: <Package />,
  },
  component: ({ title, resources }) => (
    <div>
      <h1>{title} Dashboard</h1>
      <p>Total resources: {resources?.length || 0}</p>
    </div>
  ),
  resources: [inventoryPage, categoriesPage],
});

Module Without Component (Auto-redirect)

import { defineModule, defineResource } from "@tailor-platform/app-shell";
import { BarChart } from "lucide-react";

// Automatically redirects to /reports/sales
const reportsModule = defineModule({
  path: "reports",
  meta: {
    title: "Reports",
    icon: <BarChart />,
  },
  resources: [
    defineResource({
      path: "sales",
      component: () => <SalesReport />
    }),
    defineResource({
      path: "users",
      component: () => <UserReport />
    })
  ]
});

Module with Guards

import { defineModule, pass, hidden, redirectTo } from "@tailor-platform/app-shell";

const adminModule = defineModule({
  path: "admin",
  component: AdminDashboard,
  resources: [adminResources],
  guards: [
    ({ context }) => {
      if (!context.currentUser) {
        return redirectTo("/login");
      }
      if (context.currentUser.role !== "admin") {
        return hidden(); // Shows 404
      }
      return pass();
    }
  ]
});

Module with Redirect Guard

import { defineModule, defineResource, redirectTo } from "@tailor-platform/app-shell";

const settingsModule = defineModule({
  path: "settings",
  resources: [
    defineResource({
      path: "general",
      component: () => <GeneralSettings />
    })
  ],
  guards: [() => redirectTo("settings/general")]
});

Module with Custom Error Boundary

import { defineModule, useRouteError } from "@tailor-platform/app-shell";

const CustomErrorBoundary = () => {
  const error = useRouteError() as Error;
  return (
    <div>
      <h1>Module Error</h1>
      <p>{error.message}</p>
    </div>
  );
};

const dashboardModule = defineModule({
  path: "dashboard",
  component: DashboardPage,
  resources: [overviewResource],
  errorBoundary: <CustomErrorBoundary />,
});

Module with Internationalization

import { defineModule, defineI18nLabels } from "@tailor-platform/app-shell";

const labels = defineI18nLabels({
  en: {
    productTitle: "Product Management",
  },
  ja: {
    productTitle: "製品管理",
  },
});

const productModule = defineModule({
  path: "products",
  meta: {
    title: labels.t("productTitle"),
    icon: <Package />,
  },
  component: ProductDashboard,
  resources: [productListResource],
});

Notes

  • If a module has no component and no guards, an error will be thrown
  • Modules without a component must use guards with redirectTo() or hidden() to control access
  • A guard that only returns pass() without a component will result in a blank page
  • Guards are executed in the loader phase before the component renders
  • Child resources inherit the module’s error boundary unless they define their own

Build docs developers (and LLMs) love