Skip to content

Routing

COORDINATOR uses Next.js App Router for file-based routing.

Directory Structure

app/
├── layout.tsx              # Root layout
├── page.tsx                # Home page (/)
├── api/
│   └── logs/
│       └── route.ts        # Logging API endpoint
├── (marketing)/            # Route group for public pages
│   ├── about/
│   ├── contact/
│   └── pricing/
├── login/
│   ├── page.tsx
│   └── verify-code/
├── sign-up/
│   ├── page.tsx
│   └── verify-code/
├── reset-password/
├── profile/
│   ├── layout.tsx
│   ├── page.tsx
│   ├── settings/
│   ├── organizations/
│   ├── invites/
│   └── teams/
└── portal/
    ├── page.tsx
    └── [organizationSlug]/
        ├── layout.tsx
        ├── page.tsx
        ├── pay/
        ├── accept-invite/
        ├── settings/
        └── [teamID]/
            ├── page.tsx
            ├── chat/
            ├── sessions/
            ├── context/
            ├── training/
            └── settings/

Route Groups

Marketing Routes (Public)

(marketing)/
├── about/page.tsx     → /about
├── contact/page.tsx   → /contact
└── pricing/page.tsx   → /pricing

Route groups (parentheses) don't affect the URL but help organize code.

Auth Routes

login/
├── page.tsx           → /login
└── verify-code/       → /login/verify-code

sign-up/
├── page.tsx           → /sign-up
└── verify-code/       → /sign-up/verify-code

reset-password/        → /reset-password

Profile Routes (Authenticated)

profile/
├── page.tsx           → /profile
├── settings/          → /profile/settings
├── organizations/
│   ├── page.tsx       → /profile/organizations
│   └── create/        → /profile/organizations/create
├── invites/           → /profile/invites
└── teams/             → /profile/teams

Portal Routes (Authenticated + Org Scoped)

portal/
├── page.tsx                           → /portal
└── [organizationSlug]/
    ├── page.tsx                       → /portal/my-org
    ├── pay/                           → /portal/my-org/pay
    ├── accept-invite/[inviteID]/      → /portal/my-org/accept-invite/abc
    ├── settings/
    │   ├── members/                   → /portal/my-org/settings/members
    │   ├── teams/                     → /portal/my-org/settings/teams
    │   └── billing/                   → /portal/my-org/settings/billing
    └── [teamID]/
        ├── page.tsx                   → /portal/my-org/team-1
        ├── chat/
        │   ├── page.tsx               → /portal/my-org/team-1/chat
        │   └── [chatID]/              → /portal/my-org/team-1/chat/abc
        ├── sessions/
        │   ├── page.tsx               → /portal/my-org/team-1/sessions
        │   ├── manage/                → /portal/my-org/team-1/sessions/manage
        │   ├── new/                   → /portal/my-org/team-1/sessions/new
        │   ├── forms/                 → /portal/my-org/team-1/sessions/forms
        │   └── [sessionID]/           → /portal/my-org/team-1/sessions/abc
        ├── context/
        │   ├── page.tsx               → /portal/my-org/team-1/context
        │   ├── upload/                → /portal/my-org/team-1/context/upload
        │   └── manage/
        │       ├── page.tsx           → /portal/my-org/team-1/context/manage
        │       └── [embeddingID]/     → /portal/my-org/team-1/context/manage/abc
        ├── training/
        │   ├── page.tsx               → /portal/my-org/team-1/training
        │   ├── new/                   → /portal/my-org/team-1/training/new
        │   └── jobs/
        │       ├── page.tsx           → /portal/my-org/team-1/training/jobs
        │       └── [jobID]/           → /portal/my-org/team-1/training/jobs/abc
        └── settings/
            └── team/                  → /portal/my-org/team-1/settings/team

Dynamic Segments

Segment Purpose
[organizationSlug] Organization identifier (e.g., "my-org")
[teamID] Team UUID
[sessionID] Session UUID
[chatID] Chat UUID
[embeddingID] Embedding UUID
[jobID] Training job UUID
[inviteID] Invitation UUID

Accessing Parameters

In page components:

// Server Components
export default function Page({ params }: { 
  params: { 
    organizationSlug: string; 
    teamID: string; 
  } 
}) {
  const { organizationSlug, teamID } = params;
}

In client components:

"use client";
import { useParams } from "next/navigation";

export function MyComponent() {
  const params = useParams();
  const slug = params.organizationSlug as string;
}

Layouts

Root Layout

app/layout.tsx

Wraps entire app with providers:

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <TanstackProvider>
          <ConditionalLayout>
            <Toaster />
            {children}
          </ConditionalLayout>
        </TanstackProvider>
      </body>
    </html>
  );
}

Profile Layout

app/profile/layout.tsx

Adds profile sidebar:

export default function ProfileLayout({ children }) {
  return (
    <div className="flex">
      <ProfileSidebar />
      <main>{children}</main>
    </div>
  );
}

Portal Layout

app/portal/[organizationSlug]/layout.tsx

Adds portal sidebar and role provider:

export default function PortalLayout({ children, params }) {
  return (
    <RoleProvider>
      <div className="flex">
        <PortalSidebar slug={params.organizationSlug} />
        <main>{children}</main>
      </div>
    </RoleProvider>
  );
}

Route Protection

Handled by proxy.ts middleware:

export const config = {
  matcher: [
    "/((?!api|_next/static|_next/image|favicon.ico|public).*)"
  ]
};

export async function proxy(request: NextRequest) {
  const isProtectedRoute = ["/portal", "/pay", "/profile"].some(
    path => request.nextUrl.pathname.startsWith(path)
  );

  const isAuthRoute = ["/login", "/signup"].some(
    path => request.nextUrl.pathname.startsWith(path)
  );

  const session = await getSession(request);

  if (isProtectedRoute && !session) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  if (isAuthRoute && session) {
    return NextResponse.redirect(new URL("/portal", request.url));
  }

  return NextResponse.next();
}
import Link from "next/link";

<Link href={`/portal/${slug}/${teamID}/chat`}>
  Chat
</Link>

Programmatic Navigation

import { useRouter } from "next/navigation";

const router = useRouter();
router.push(`/portal/${slug}/${teamID}/sessions/${sessionId}`);

Prefetching

Next.js automatically prefetches visible links. For manual control:

<Link href="/path" prefetch={false}>
  Don't prefetch
</Link>

API Routes

Only one API route for client logging:

app/api/logs/route.ts → POST /api/logs

All other API calls go to external services via fetchJSON.