Skip to Content

Routing

This document explains the routing system used in the Rhesis frontend application, which is built with Next.js App Router.

App Router Overview

The Rhesis frontend uses Next.js App Router, which provides a file-system based routing approach where:

  • Folders define routes
  • Files define UI
  • Special files handle specific functionality (layout, page, error, not-found, etc.)
  • Dynamic segments are supported with brackets notation

Route Structure

App Router Structure
└── 
src/app
    ├── 
(protected)# Route group for authenticated pages (24 top-level routes)
├── 
dashboard# Dashboard route
├── 
projects# Projects route
    │   │   ├── 
[identifier]# Dynamic project route
    │   │   └── 
page.tsx# Projects list page
├── 
architect# Architect chat route
├── 
...# tests, test-runs, traces, endpoints, playground, and other routes
├── 
layout.tsx# Protected layout with session check
├── 
error.tsx# Shared error boundary for protected routes
└── 
not-found.tsx# Shared 404 for protected routes
    ├── 
api# Route handlers, including the backend proxy
├── 
backend/[...path]# BFF proxy — injects auth from the session cookie
└── 
[...path]# Catch-all proxy for routes without a dedicated handler
    ├── 
auth# Authentication routes
├── 
signin# OAuth/auth-code callback handling
├── 
register# Registration route
└── 
magic-link# Magic-link verification
    ├── 
layout.tsx# Root layout
    ├── 
page.tsx# Home page (Quick Start / unified login landing)
    └── 
not-found.tsx# Root 404

Route Groups

The (protected) route group organizes authenticated pages without affecting the URL structure. There is no separate (public) group — public routes (/, /auth/*) simply live outside (protected).

Layouts

Layouts share UI between multiple pages:

  1. Root Layout (app/layout.tsx): theme provider, global providers, top-level metadata, and the runtime window.__ENV__ injection script
  2. Protected Layout (app/(protected)/layout.tsx): session check, main navigation (AppShell/Sidebar), feature-flag provider

For client-side navigation, use the Next.js Link component:

navigation.tsx
import Link from 'next/link';

<Link href="/projects">Projects</Link>

Programmatic Navigation

For programmatic navigation, use the useRouter hook:

programmatic-navigation.tsx
import { useRouter } from 'next/navigation';

const router = useRouter();
router.push('/projects');

Route Protection

Next.js 16 renamed the edge middleware convention from middleware.ts to src/proxy.ts. Route protection happens in two layers:

  1. src/proxy.ts: runs on every matched request (see its config.matcher), decodes the session JWT locally, refreshes the access token via POST /auth/refresh only when it’s within 60 seconds of expiry, and redirects to /onboarding if the decoded token has no organization. Public paths are defined in src/constants/paths.ts (isPublicPath()), not a (public) route group.
  2. Protected layout / page checks: app/(protected)/layout.tsx and app/layout.tsx call NextAuth’s auth() server-side to gate rendering.

There is no auth-token cookie check — the session is a NextAuth JWE cookie, decoded rather than looked up by name. See Frontend Authentication for the full session/token-refresh flow.

Dynamic Routes

Dynamic routes use parameters in the URL, defined with brackets notation. This codebase uses [identifier], not [id]:

  • /projects/[identifier]: Project detail page
  • /tests/[identifier]: Test detail page

Access parameters in the page component:

dynamic-route.tsx
// app/(protected)/projects/[identifier]/page.tsx
export default async function ProjectPage({ params }: { params: Promise<{ identifier: string }> }) {
const { identifier } = await params;
// Use the identifier to fetch project data
return <div>Project {identifier}</div>;
}

Error Handling

app/(protected)/error.tsx is a shared client-side error boundary for every route under (protected) (there is no per-route error.tsx in this app):

app/(protected)/error.tsx
"use client";

export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
);
}

Not Found Pages

app/not-found.tsx (root) and app/(protected)/not-found.tsx (protected routes) handle 404s — both shared, not defined per dynamic route.

Metadata

Page metadata is defined using the metadata export, typically in a route’s layout.tsx:

app/(protected)/projects/layout.tsx
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Projects | Rhesis",
};

export default function ProjectsLayout({ children }: { children: React.ReactNode }) {
return children;
}

Best Practices

  1. Keep Pages Thin: Page components should focus on data fetching and layout, with most UI logic in components
  2. Client Components: Use the 'use client' directive only when needed for interactivity
  3. Parallel Routes: Consider parallel routes for complex layouts with independent navigation
  4. Intercepting Routes: Consider intercepting routes for modals and overlays