This document describes the navigation and routing system in the Trigger.dev webapp. It covers:
pathBuilder.ts utility providing type-safe URL generation functionsSideMenu component providing the primary navigation interfaceuseOrganization, useProject, useEnvironment)SelectBestEnvironmentPresenterFor information about the Remix application architecture and route loaders, see Application Architecture. For details about environment management and switching, see Environment Management. For UI component primitives used in navigation, see UI Components and Styling.
Sources: apps/webapp/app/utils/pathBuilder.ts1-504 apps/webapp/app/components/navigation/SideMenu.tsx1-623
The webapp implements a hierarchical URL structure that reflects the multi-tenant nature of Trigger.dev. Every resource is scoped to an organization, project, and environment, creating a nested routing pattern. This structure maps directly to Remix route files in apps/webapp/app/routes/.
URL Hierarchy Diagram:
Route File Mapping:
| URL Path | Remix Route File | Key Loader Data |
|---|---|---|
/account | _app.account/route.tsx | User profile data |
/orgs/:organizationSlug | _app.orgs.$organizationSlug/route.tsx | Organizations list, selected org, project, environment |
/orgs/:organizationSlug/projects/:projectParam | _app.orgs.$organizationSlug.projects.$projectParam/route.tsx | Project details, redirects to environment |
/orgs/.../env/:envParam | _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam/route.tsx | Environment-specific data, tasks list |
/orgs/.../runs | _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs/route.tsx | Task runs with filters |
/orgs/.../runs/:runParam | _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx | Run details, spans, logs |
Sources: apps/webapp/app/utils/pathBuilder.ts1-508 apps/webapp/app/routes/
The routing system uses type-safe parameter types to represent entities in URLs:
| Type | TypeScript Definition | Description | Example |
|---|---|---|---|
OrgForPath | Pick<Organization, "slug"> | Organization identifier | { slug: "acme-corp-a1b2" } |
ProjectForPath | Pick<Project, "slug"> | Project identifier | { slug: "my-project-c3d4" } |
EnvironmentForPath | Pick<RuntimeEnvironment, "slug"> | Environment identifier | { slug: "prod" } or { slug: "dev-user123" } |
v3RunForPath | Pick<TaskRun, "friendlyId"> | Task run identifier | { friendlyId: "run_abc123" } |
v3SpanForPath | Pick<TaskRun, "spanId"> | Span identifier | { spanId: "span_xyz789" } |
DeploymentForPath | Pick<WorkerDeployment, "shortCode"> | Deployment identifier | { shortCode: "deploy_xyz" } |
TaskForPath | { taskIdentifier: string } | Task identifier | { taskIdentifier: "my-task" } |
These types use TypeScript's Pick utility to extract only the slug or identifier field from database models, ensuring path functions receive minimal, focused data.
Sources: apps/webapp/app/utils/pathBuilder.ts1-16
The pathBuilder.ts utility provides type-safe functions for generating URLs throughout the application. All URL generation goes through these functions to ensure consistency and prevent broken links.
Function Hierarchy by Scope:
Sources: apps/webapp/app/utils/pathBuilder.ts51-504
All path functions follow a consistent pattern, building URLs from base paths. The implementation uses helper functions to extract slug values and compose URLs hierarchically.
Core implementation from pathBuilder.ts:
Query Parameter Handling:
The objectToSearchParams utility from ~/utils/searchParams converts filter objects to URLSearchParams, enabling type-safe filter URLs:
Usage examples:
Sources: apps/webapp/app/utils/pathBuilder.ts96-447 apps/webapp/app/utils/searchParams.ts
The system uses Zod schemas to validate URL parameters in route loaders. Each schema extends the previous one, building a hierarchy:
| Schema | Parameters | TypeScript Type | Purpose |
|---|---|---|---|
OrganizationParamsSchema | organizationSlug: string | { organizationSlug: string } | Validates organization routes |
ProjectParamSchema | organizationSlug, projectParam | { organizationSlug: string, projectParam: string } | Validates project routes |
EnvironmentParamSchema | organizationSlug, projectParam, envParam | { organizationSlug: string, projectParam: string, envParam: string } | Validates environment routes |
v3TaskParamsSchema | Above + taskParam | EnvironmentParamSchema & { taskParam: string } | Validates task routes |
v3RunParamsSchema | Above + runParam | EnvironmentParamSchema & { runParam: string } | Validates run detail routes |
v3SpanParamsSchema | Above + spanParam | v3RunParamsSchema & { spanParam: string } | Validates span detail routes |
v3DeploymentParams | Above + deploymentParam | EnvironmentParamSchema & { deploymentParam: string } | Validates deployment routes |
v3ScheduleParams | Above + scheduleParam | EnvironmentParamSchema & { scheduleParam: string } | Validates schedule routes |
Schema definitions:
Usage in route loaders:
Sources: apps/webapp/app/utils/pathBuilder.ts18-49
Organizations and projects use unique slugs in URLs. Slugs are generated from user-provided names with collision detection and retry logic.
The createOrganization function generates slugs using a combination of the user-provided title and a 4-character random suffix:
slug librarynanoid with alphabet 1234567890abcdefSources: apps/webapp/app/models/organization.server.ts18-77
Project slug generation follows the same pattern:
Both use recursive retry logic with an attemptCount parameter that throws an error after 100 attempts, though in practice collisions are extremely rare due to the 4-character suffix (65,536 possible combinations).
Sources: apps/webapp/app/models/project.server.ts19-107
The SideMenu component provides the primary navigation interface, displaying organization/project selection, environment switching, and navigation links for all main sections.
Sources: apps/webapp/app/components/navigation/SideMenu.tsx112-350
The SideMenuItem component renders each navigation link with consistent styling and active state detection. Menu items are organized into three sections: Main Navigation, Waitpoints, and Manage.
Main Navigation Items (lines 218-266):
| Menu Item | Icon | Path Function | Active Color | Data Action |
|---|---|---|---|---|
| Tasks | TaskIconSmall | v3EnvironmentPath | text-tasks | "tasks" |
| Runs | RunsIconExtraSmall | v3RunsPath | text-runs | (none) |
| Batches | Squares2X2Icon | v3BatchesPath | text-batches | "batches" |
| Schedules | ClockIcon | v3SchedulesPath | text-schedules | "schedules" |
| Queues | RectangleStackIcon | v3QueuesPath | text-queues | "queues" |
| Deployments | ServerStackIcon | v3DeploymentsPath | text-deployments | "deployments" |
| Test | BeakerIcon | v3TestPath | text-tests | "test" |
Waitpoints Section (lines 269-277):
| Menu Item | Icon | Path Function | Active Color | Badge |
|---|---|---|---|---|
| Tokens | WaitpointTokenIcon | v3WaitpointTokensPath | text-sky-500 | <V4Badge /> |
Manage Section (lines 279-331):
| Menu Item | Icon | Path Function | Active Color | Badge |
|---|---|---|---|---|
| Bulk actions | ListCheckedIcon | v3BulkActionsPath | text-bulkActions | (none) |
| API keys | KeyIcon | v3ApiKeysPath | text-apiKeys | (none) |
| Environment variables | IdentificationIcon | v3EnvironmentVariablesPath | text-environmentVariables | (none) |
| Alerts | BellAlertIcon | v3ProjectAlertsPath | text-alerts | (none) |
| Preview branches | BranchEnvironmentIconSmall | branchesPath | text-preview | <V4Badge /> |
| Regions | GlobeAmericasIcon | regionsPath | text-green-500 | <V4Badge /> |
| Project settings | Cog8ToothIcon | v3ProjectSettingsPath | text-projectSettings | (none) |
The data-action attributes on menu items enable analytics tracking. The <V4Badge /> component marks features introduced in v4 of the platform.
Sources: apps/webapp/app/components/navigation/SideMenu.tsx218-331 apps/webapp/app/components/navigation/SideMenuItem.tsx
The ProjectSelector component at the top of the SideMenu provides organization and project switching via a Popover menu.
Component Structure:
Key implementation details:
SwitchOrganizations submenu:
Uses a nested Popover with hover-based opening:
The component features:
useEffect watching navigation.location?.pathnameisManagedCloud feature flagcurrentPlanisSelected prop and FolderOpenIcon vs FolderIconSources: apps/webapp/app/components/navigation/SideMenu.tsx352-597
The SideMenu adapts its UI based on user permissions and context:
Admin Dashboard Link (lines 160-173):
Displays an admin dashboard button for admin users, or an impersonation banner when an admin is impersonating another user.
Dev Connection Indicator (lines 188-214):
For development environments using the V2 engine, displays a connection status indicator showing whether the local dev server is connected:
The ConnectionIcon changes appearance based on isConnected state from useDevPresence() hook.
Free Plan Usage (lines 340-345):
Displays usage percentage for users on the free plan, linking to billing settings.
Header Scroll Detection (lines 119-138):
The SideMenu implements scroll-based header border visibility:
This creates a subtle visual indicator (border transition on line 149) when the menu is scrolled past 1px.
Sources: apps/webapp/app/components/navigation/SideMenu.tsx119-345 apps/webapp/app/components/DevPresence.tsx apps/webapp/app/components/billing/FreePlanUsage.tsx
The webapp uses Remix's route matching system with custom hooks to access loader data throughout the component tree.
The useMatchesData utility searches all matched routes for specific loader data. This is a foundational hook used by all navigation data hooks.
Implementation (lines 44-66):
Key features:
"routes/_app.orgs.$organizationSlug" or ["route-a", "route-b"])reduce to short-circuit on first founddebug: trueRemix's useMatches() returns all currently matched routes in the route hierarchy (e.g., root route, organization route, project route, environment route all matched simultaneously). This allows any component to access data from parent loaders through the route tree.
Type-safe wrapper:
The useTypedMatchesData hook provides type inference from route loaders, eliminating the need for manual type assertions.
Sources: apps/webapp/app/utils.ts44-77
Specialized hooks provide type-safe access to organization, project, and environment data from route loaders.
Hook Overview:
| Hook | Route Match ID | Returns | Throws if Missing |
|---|---|---|---|
useOptionalOrganizations | organizationMatchId | MatchedOrganization[] or undefined | No |
useOrganizations | organizationMatchId | MatchedOrganization[] | Yes (invariant) |
useOptionalOrganization | organizationMatchId | MatchedOrganization or undefined | No |
useOrganization | organizationMatchId | MatchedOrganization | Yes (invariant) |
useOptionalProject | organizationMatchId | MatchedProject or undefined | No |
useProject | organizationMatchId | MatchedProject | Yes (invariant) |
useOptionalEnvironment | organizationMatchId | MatchedEnvironment or undefined | No |
useEnvironment | organizationMatchId | MatchedEnvironment | Yes (invariant) |
useIsImpersonating | organizationMatchId | boolean | No |
Constant definitions:
Hook implementations from useOrganizations.ts and useProject.tsx:
The invariant function throws an error if the condition is false, ensuring that components using required hooks (useOrganization, useProject, etc.) only run when the data is available.
Usage pattern in components:
Change detection hooks from useOrganizations.ts (lines 50-64):
These hooks enable components to react to organization or project changes via callback functions. Common use cases include:
Example usage:
Sources: apps/webapp/app/hooks/useOrganizations.ts1-64 apps/webapp/app/hooks/useProject.tsx1-29 apps/webapp/app/hooks/useChanged.ts
When a user navigates to a project without specifying an environment, the system automatically selects the most appropriate environment using SelectBestEnvironmentPresenter.
Automatic environment redirect:
Route implementation:
The SelectBestEnvironmentPresenter selection logic prioritizes:
orgMember.userId)This ensures users are directed to their personal development environment when available, or to the shared production environment otherwise.
Sources: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx1-44 apps/webapp/app/presenters/SelectBestEnvironmentPresenter.server.ts
The AppLayout module provides container components that structure the page layout and work with the navigation system.
Layout Components:
| Component | Purpose | Key Classes |
|---|---|---|
AppContainer | Root container for entire app | grid h-full w-full grid-rows-1 overflow-hidden |
MainBody | Contains page content next to sidebar | grid grid-rows-1 overflow-hidden |
PageContainer | Wrapper for page with header and body | grid-rows-[auto_1fr] overflow-hidden |
PageBody | Scrollable content area | overflow-y-auto scrollbar-thin with padding |
MainCenteredContainer | Centered container for auth/settings pages | mx-auto max-w-xs md:mt-[22vh] |
Sources: apps/webapp/app/components/layout/AppLayout.tsx1-86
The routing system includes protection against open redirect vulnerabilities through the sanitizeRedirectPath utility:
This function ensures that redirect paths:
/ but not //This prevents malicious redirect attacks where an attacker might try to redirect users to external sites via query parameters.
Sources: apps/webapp/app/utils.ts4-42
Refresh this wiki
This wiki was recently refreshed. Please wait 7 days to refresh again.