Docs/routingv1.0.0-rc.1

Route Conventions

RouteConventionEngine scans a directory tree and converts its file structure into an array of RouteConvention objects. These conventions are then consumed by AutoRouter to register live route handlers.

Key Concepts

  • Pages directory. By default, RouteConventionEngine looks for a pages/ subdirectory inside the app directory. All .ts, .tsx, .js, and .jsx files within it become routes.
  • File-to-route mapping. File names map to URL segments. index.ts files resolve to the directory path. Dynamic segments use the [param] bracket syntax. Catch-all segments use [...param].
  • Route type. Files inside pages/api/ (the configurable apiDir, as the first directory under pages/) become type: 'api' routes. All other files, including pages/capital.ts, become type: 'page' routes.
  • Layout discovery. findLayout walks up the directory tree from each route file until it finds a _layout.ts or layout.ts file. The nearest layout wins.
  • Middleware discovery. findMiddleware collects all _middleware.ts files from the route's directory up to the pages root, ordering them from outermost to innermost.
  • Sort order. Routes are sorted by specificity: static routes before dynamic ones, longer paths before shorter ones, then alphabetically.

API Reference

RouteConvention

interface RouteConvention {
  path: string;          // resolved URL (e.g., /users/:id)
  filePath: string;      // absolute path to source file
  type: 'page' | 'api'; // route category
  params: string[];      // dynamic parameter names
  layout?: string;       // absolute path to layout file
  middleware?: string[]; // absolute paths to middleware files (outer first)
}

RoutingConventions

Configuration object passed to the engine constructor:

interface RoutingConventions {
  pagesDir: string;          // default: 'pages'
  apiDir: string;            // default: 'api'
  layoutFiles: string[];     // default: ['_layout.ts', 'layout.ts']
  middlewareFiles: string[]; // default: ['_middleware.ts']
  indexFiles: string[];      // default: ['index.ts', 'index.tsx']
  dynamicPattern: RegExp;    // default: /\[([^\]]+)\]/
  catchAllPattern: RegExp;   // default: /\[\.\.\.([^\]]+)\]/
}

RouteConventionEngine

class RouteConventionEngine {
  constructor(conventions?: Partial<RoutingConventions>)

  async scanRoutes(appDir: string): Promise<RouteConvention[]>
  fileToRoute(filePath, pagesPath, appDir): RouteConvention | null
  pathToRoute(filePath: string): string
  extractParams(filePath: string): string[]
  determineRouteType(filePath: string): 'page' | 'api'
  findLayout(filePath, appDir): string | undefined
  findMiddleware(filePath, appDir): string[]
  sortRoutes(routes: RouteConvention[]): RouteConvention[]
  isSpecialFile(filePath: string): boolean
}

createRouteConventions(conventions?)

Factory function that returns a RouteConventionEngine with optional overrides.

function createRouteConventions(
  conventions?: Partial<RoutingConventions>
): RouteConventionEngine

describeRoutingConventions()

Returns a multi-line string describing the routing conventions in a human-readable format.

function describeRoutingConventions(): string

File-to-Route Mapping Rules

File path (relative to pages/) URL route
index.ts /
about.ts /about
users/index.ts /users
users/[id].ts /users/:id
users/[id]/edit.ts /users/:id/edit
blog/[...slug].ts /blog/*
api/users.ts /api/users (type: 'api')
api/users/[id].ts /api/users/:id (type: 'api')

Special files that are not converted to routes:

  • _layout.ts, layout.ts
  • _middleware.ts
  • _app.ts, _document.ts, _error.ts

Basic Usage

import { createRouteConventions } from '@tagna/udiot';
import * as path from 'path';

const engine = createRouteConventions();
const appDir = path.resolve('./apps/my-app');
const routes = await engine.scanRoutes(appDir);

for (const route of routes) {
  console.log(`${route.type.toUpperCase().padEnd(4)} ${route.path}`);
  if (route.params.length > 0) {
    console.log(`       params: ${route.params.join(', ')}`);
  }
}

Common Patterns

Directory structure and the routes it produces

apps/my-app/
  pages/
    index.ts                    → GET  /
    about.ts                    → GET  /about
    _layout.ts                  (layout for all pages, not a route)
    users/
      index.ts                  → GET  /users
      [id].ts                   → GET  /users/:id
      [id]/
        edit.ts                 → GET  /users/:id/edit
        posts.ts                → GET  /users/:id/posts
      _middleware.ts            (applied to /users/* routes)
    api/
      users.ts                  → API  /api/users
      users/
        [id].ts                 → API  /api/users/:id

Custom conventions (different page directory name)

import { createRouteConventions } from '@tagna/udiot';

const engine = createRouteConventions({
  pagesDir: 'routes',      // use 'routes/' instead of 'pages/'
  apiDir: 'handlers',      // API files inside 'routes/handlers/'
  indexFiles: ['index.ts', 'route.ts'],
});

Inspecting special-file detection

const engine = createRouteConventions();
console.log(engine.isSpecialFile('/app/pages/_middleware.ts')); // true
console.log(engine.isSpecialFile('/app/pages/users/index.ts')); // false

Integration Notes

  • scanRoutes uses fs.promises.readdir with withFileTypes: true, so it requires Node.js 10.10+.
  • findLayout and findMiddleware use fs.existsSync for each candidate file. In large directory trees this can involve many synchronous filesystem checks. Consider caching the result.
  • The catchAllPattern is applied before dynamicPattern to avoid [...slug] being partially matched by the dynamic segment regex. The order of substitution is: catch-all first, then single-param.

Pitfalls

  • Windows backslash path separators are normalized to forward slashes inside pathToRoute. However, filePath values in RouteConvention.filePath retain the OS-native separator. Pass absolute paths to import() to avoid resolution issues.
  • Files whose names start with _ are never routes: besides _layout, _middleware, _app, _document and _error, use the prefix for helpers kept next to pages. Layout and middleware files are found as .ts, .js or .mjs.
  • Route sorting places static routes before dynamic ones within the same depth. Two static routes at the same depth are sorted alphabetically. Ensure your route handlers do not depend on a different priority order.
From the canonical UDIOT public documentation.Try it in the playground ↗