App Routes Path Strategy

By Claude Sonnet 4.5*🤖 AI-Generated (100%)·  January 26, 2026
docs
🏷️ Tags:architectureroutingmetadatadesign-decision

Analysis and decision on how to handle path metadata for app routes: derive from file structure vs explicit metadata.

The Problem

App routes live in src/app/ with file-based routing. We want to list them alongside content in navigation, search, and the homepage. The question: should we store the URL path in metadata, or derive it?

Options Considered

Option 1: Always Derive Path

Calculate path from file structure in getAllAppRoutes():

.ts
// app/metadata-scanner/page.tsx → /metadata-scanner
// app/dev/test-parser/page.tsx → /dev/test-parser

Pros:

Cons:

Option 2: Always Specify Path in Metadata

Require path in every routeMetadata export:

.ts
export const routeMetadata = {
  title: 'Metadata Scanner',
  slug: 'metadata-scanner',
  path: '/metadata-scanner',  // Required
  // ...
};

Pros:

Cons:

Option 3: Optional Path Override (Recommended)

Derive path by default, allow metadata to override:

.ts
// Default: path derived from file location
export const routeMetadata = {
  title: 'Metadata Scanner',
  slug: 'metadata-scanner',
  // No path needed - will be /metadata-scanner
};

// Override when needed
export const routeMetadata = {
  title: 'Parser Test',
  slug: 'test-parser',
  path: '/dev/parser',  // Override: file is at app/dev/test-parser but want /dev/parser
};

Pros:

Cons:

Decision: Option 3

Use optional path override. This minimizes redundancy while preserving flexibility for the rare case where URL structure should differ from file structure.

Implementation

In getAllAppRoutes()

.ts
export async function getAllAppRoutes() {
  // ...scan files...
  
  for (const pagePath of pageFiles) {
    const derivedPath = '/' + pagePath.replace('/page.tsx', '').replace('page.tsx', '');
    const module = await import(`@/app/${pagePath.replace('.tsx', '')}`);
    
    if (module.routeMetadata) {
      routes.push({
        filename: pagePath,
        metadata: {
          ...module.routeMetadata,
          // Use metadata path if provided, otherwise use derived path
          path: module.routeMetadata.path || derivedPath,
        },
      });
    }
  }
}

In PostMeta Interface

.ts
export interface PostMeta {
  // ... other fields ...
  path?: string; // Optional - overrides derived path for app routes
}

In Navigation/Routing Logic

.ts
// HomePage, Navigator, etc.
const href = item.metadata.path || (
  // Fallback for content files without path
  item.metadata.type === 'doc:commit' 
    ? `/docs/commits/${item.metadata.slug}`
    : item.metadata.type === 'doc'
    ? `/docs/${item.metadata.slug}`
    : `/posts/${item.metadata.slug}`
);

Examples

Standard Case (No Override)

.ts
// File: app/metadata-scanner/page.tsx
export const routeMetadata = {
  title: 'Metadata Scanner',
  slug: 'metadata-scanner',
  description: '...',
  // NO path specified
};

// Result: path auto-derived as "/metadata-scanner"

Override Case

.ts
// File: app/experimental/new-feature/page.tsx
export const routeMetadata = {
  title: 'New Feature',
  slug: 'new-feature',
  path: '/features/new',  // Override: want /features/new instead of /experimental/new-feature
};

// Result: path is "/features/new"

When to Use Path Override

Conclusion

The optional path override strategy balances DRY principles with practical flexibility. Most routes need no path metadata. The few that do can override cleanly without polluting every route's metadata.