# How to Manage Nested Routes in OmniRoute: Next.js App Router Implementation Guide

> Learn to manage nested routes in OmniRoute with Next.js App Router. Implement dynamic and catch-all routes for complex API endpoints, boosting your application's structure.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-08-17

---

**OmniRoute leverages Next.js 16 App Router's file-system routing to create nested API endpoints by mapping directory hierarchies to URL paths, using `[param]` for dynamic segments and `[...param]` for catch-all routes that handle complex identifiers like provider-prefixed model names.**

Learning how to manage nested routes in OmniRoute is essential for developers working with this open-source API gateway. The project uses Next.js 16 file-system conventions to structure its REST API under `src/app/api/v1/`, where every folder represents a URL segment and special patterns handle dynamic routing.

## Understanding OmniRoute's File-System Routing Structure

OmniRoute exposes every public API endpoint under `src/app/api/v1/`, where the directory hierarchy mirrors the final URL structure. The routing system recognizes three distinct folder naming patterns:

| Pattern | Meaning | Example Path |
|---------|---------|--------------|
| [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) | Leaf file implementing one or more HTTP verbs | [`src/app/api/v1/chat/completions/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/chat/completions/route.ts) |
| `[param]` | Single-segment dynamic parameter injected into `params` | `src/app/api/v1/registered-keys/[id]/route.ts` |
| `[...param]` | Catch-all capturing remaining URL segments including slashes | `src/app/api/v1/models/[...model]/route.ts` |

Static routes take precedence over dynamic ones, ensuring concrete folders like `chat/completions` match before dynamic segments are evaluated.

## Dynamic Route Patterns in OmniRoute

### Single Segment Parameters ([param])

Dynamic parameters capture individual URL segments as typed values. In `src/app/api/v1/registered-keys/[id]/route.ts`, the `[id]` folder extracts the identifier from the path and injects it into the handler's `params` promise as a string.

### Catch-All Segments ([...param])

Catch-all routes capture everything remaining in the URL path. According to the OmniRoute source code, this pattern serves two critical purposes:

- **Provider-prefixed identifiers**: The models endpoint at `src/app/api/v1/models/[...model]/route.ts` uses this to capture identifiers containing slashes (e.g., `claude/sonnet-3`) without URL encoding issues.
- **Global error handling**: The `src/app/api/v1/[...omnirouteCatchAll]/route.ts` file acts as a fallback for unknown `/v1/*` paths, returning structured JSON errors with `type: "not_found"` instead of HTML 404 pages.

## Implementing Nested Sub-Resources

Nested folders represent sub-resources, creating intuitive API hierarchies. The revocation endpoint demonstrates how OmniRoute handles deep nesting:

```typescript
// src/app/api/v1/registered-keys/[id]/revoke/route.ts
import { NextResponse } from "next/server";
import { isAuthenticated } from "../../../_shared/auth";

export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
  if (!(await isAuthenticated(request))) {
    return NextResponse.json({ error: { message: "Authentication required" } }, { status: 401 });
  }
  const { id } = await params;
  // Database revocation logic here
  return NextResponse.json({ revoked: id, status: "success" });
}

```

When a client sends `POST /api/v1/registered-keys/abc123/revoke`, Next.js resolves the path to this nested directory, extracts `abc123` as `id`, and executes the revocation logic.

## Handling Special Identifiers with Catch-All Routes

When resource identifiers contain slashes—common in AI model names—OmniRoute uses catch-all segments to preserve the full identifier intact:

```typescript
// src/app/api/v1/models/[...model]/route.ts
export async function GET(_: Request, { params }: { params: Promise<{ model: string[] }> }) {
  const { model } = await params;
  const fullId = decodeURIComponent(model.join("/")); // Reconstructs "claude/sonnet-3"
  return handleGetModelById(_, fullId, getUnifiedModelsResponse);
}

```

The `model` parameter arrives as an array of path segments, which the handler joins to reconstruct the original identifier before querying the database.

## Cross-Cutting Concerns and Shared Utilities

Rather than duplicating logic across nested routes, OmniRoute imports shared utilities from `src/app/api/v1/_shared/*`:

- **`handleCorsOptions`**: Standardized CORS preflight responses
- **`isAuthenticated`**: JWT verification and session validation  
- **Error shaping utilities**: Consistent JSON error formatting used by [`Response.json`](https://github.com/diegosouzapw/OmniRoute/blob/main/Response.json) across all endpoints

This architectural pattern keeps individual route files minimal while ensuring consistent security, rate limiting, and observability throughout the nested route hierarchy.

## Summary

- OmniRoute maps URL paths directly to the directory structure under `src/app/api/v1/` using Next.js 16 App Router conventions.
- Use `[param]` folders for single dynamic segments and `[...param]` for catch-all routes that capture multiple path segments.
- The global catch-all at `src/app/api/v1/[...omnirouteCatchAll]/route.ts` ensures API clients receive JSON 404 errors instead of HTML pages.
- Catch-all routes enable support for slash-containing identifiers like `provider/model-name` without URL encoding complexity.
- Shared utilities in `src/app/api/v1/_shared/*` provide consistent authentication, CORS handling, and error formatting across all nested routes.

## Frequently Asked Questions

### What is the difference between [param] and [...param] in OmniRoute?

The `[param]` syntax captures a single URL segment as a string value, suitable for IDs like `abc123`. The `[...param]` catch-all syntax captures all remaining path segments as an array, enabling OmniRoute to handle identifiers containing slashes (such as `claude/sonnet-3`) or to create global fallback handlers for unmatched routes.

### How does OmniRoute handle 404 errors for unknown API paths?

OmniRoute implements a global catch-all route at `src/app/api/v1/[...omnirouteCatchAll]/route.ts` that intercepts any unmatched `/v1/*` requests. Instead of allowing Next.js to serve an HTML 404 page, this handler returns a JSON response with `type: "not_found"`, maintaining consistent JSON communication that API clients and SDKs expect.

### Can I add custom sub-resources to existing OmniRoute endpoints?

Yes, you can extend OmniRoute by creating additional nested directories under `src/app/api/v1/`. For example, to add a `validate` sub-resource to registered keys, create `src/app/api/v1/registered-keys/[id]/validate/route.ts` and export the appropriate HTTP method handlers. The file-system routing automatically wires the new endpoint to `POST /api/v1/registered-keys/{id}/validate` without additional configuration.

### Where does OmniRoute store shared logic used by multiple routes?

Shared utilities, including authentication checks (`isAuthenticated`), CORS handling (`handleCorsOptions`), and error formatting, reside in `src/app/api/v1/_shared/`. Route files import these helpers to maintain consistency and avoid code duplication while keeping individual route implementations focused on specific business logic.