Skip to main content

Adding New Pages

BunShip Pro uses TanStack Router with file-based routing. Create a file and you have a route.

How Routing Works

Routes live in apps/web/src/routes/. The file name becomes the URL path:
FileURL
routes/index.tsx/
routes/_app/dashboard.tsx/dashboard
routes/_app/organizations_.$orgId/members.tsx/organizations/:orgId/members
routes/_marketing/pricing.tsx/pricing

Layout Groups

Files are organized into layout groups using the underscore prefix:
PrefixLayoutPurpose
_app/Authenticated layout with sidebarDashboard, settings, org pages
_auth/Minimal centered layoutLogin, register, password reset
_marketing/Marketing header/footerPricing, blog, contact, legal
The layout file (e.g., _app.tsx) wraps all pages in that group with shared UI — sidebar, header, auth protection.

Adding a Dashboard Page

  1. Create a file in routes/_app/:
// apps/web/src/routes/_app/analytics.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/_app/analytics")({
  component: AnalyticsPage,
});

function AnalyticsPage() {
  return (
    <div className="space-y-6">
      <div>
        <h1 className="text-2xl font-semibold tracking-tight">Analytics</h1>
        <p className="text-sm text-muted-foreground">View your usage analytics.</p>
      </div>

      {/* Your page content */}
    </div>
  );
}
  1. The page is now accessible at /analytics (authentication required automatically via the _app layout).

Adding an Organization Page

Organization pages are scoped to a specific org and have access to the org ID from the URL:
// apps/web/src/routes/_app/organizations_.$orgId/reports.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/_app/organizations_/$orgId/reports")({
  component: ReportsPage,
});

function ReportsPage() {
  const { orgId } = Route.useParams();

  return (
    <div className="space-y-6">
      <div>
        <h1 className="text-2xl font-semibold tracking-tight">Reports</h1>
        <p className="text-sm text-muted-foreground">Generate and view reports.</p>
      </div>

      {/* Use orgId to fetch org-specific data */}
    </div>
  );
}
This page is available at /organizations/:orgId/reports.

Adding to the Sidebar

The sidebar navigation is defined in apps/web/src/components/sidebar.tsx. There are two navigation arrays: Add items to the DEFAULT_SECTIONS array:
const DEFAULT_SECTIONS: NavSection[] = [
  {
    label: "Workspace",
    items: [
      { label: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
      { label: "Organizations", href: "/organizations", icon: Building2 },
      { label: "Analytics", href: "/analytics", icon: BarChart3 }, // new
    ],
  },
  // ...
];

Organization Navigation

Add items to the ALL_ORG_ITEMS array. Each item belongs to either the org section (user-facing) or dev section (developer tools):
const ALL_ORG_ITEMS: Array<NavItem & { section: "org" | "dev" }> = [
  // existing items...
  { section: "dev", label: "Reports", href: "/reports", icon: FileText }, // new
];
Import icons from lucide-react.

Protecting Pages by Role

The organization layout provides role context. Use the useOrgContext() hook to check the current user’s role:
import { useOrgContext } from '../organizations_.$orgId'

function ReportsPage() {
  const { role } = useOrgContext()

  // Hide admin-only sections
  if (role === 'viewer') {
    return <p className="text-sm text-muted-foreground">You don't have access to reports.</p>
  }

  return (
    // Full page content
  )
}

Role Hierarchy

Roles are hierarchical: owner > admin > member > viewer. Permissions are defined in packages/config/src/features.ts:
permissions: {
  owner: ["*"],
  admin: ["org:read", "org:update", "members:*", "billing:read", ...],
  member: ["org:read", "members:read", "projects:*", "files:read", "files:upload"],
  viewer: ["org:read", "members:read", "projects:read", "files:read"],
}

Adding a Marketing Page

Marketing pages use the _marketing layout with the public header and footer:
// apps/web/src/routes/_marketing/about.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/_marketing/about")({
  component: AboutPage,
});

function AboutPage() {
  return (
    <div className="mx-auto max-w-4xl px-6 py-24">
      <h1 className="text-4xl font-bold tracking-tight">About Us</h1>
      <p className="mt-4 text-lg text-muted-foreground">Our story...</p>
    </div>
  );
}
To add it to the marketing header navigation, edit apps/web/src/components/marketing-header.tsx.

Next Steps