Skip to content

Proposal: Virtual route groups #257

@terrablue

Description

@terrablue

Motivation

Sometimes it's useful to group routes without creating a URL segment, mainly to:

  • apply a layout to a subset of routes under a directory,
  • keep route trees readable without forcing "layout boundaries" to match URL boundaries,
  • avoid "global-at-this-level" layouts when only some siblings should share it.

Example intent:

  • A shared layout for a set of reserved top-level routes:
    • routes/(reserved)/projects.ts
    • routes/(reserved)/namespaces.ts
    • routes/(reserved)/+layout.ts

…but not for everything in routes/.

And similarly under a dynamic segment:

  • routes/[namespace]/(reserved)/index.ts
  • routes/[namespace]/(reserved)/settings.ts
  • routes/[namespace]/(reserved)/projects.ts
  • routes/[namespace]/(reserved)/+layout.ts

You get a clean URL structure (/projects, /namespaces, /acme/settings, etc.) while still having layout scoping.

Proposed Solution

1) Introduce "virtual group" directories

Any directory whose name is wrapped in parentheses is a virtual grouping directory:

  • routes/(reserved)/...
  • routes/(admin)/...
  • routes/(marketing)/...

A virtual group directory:

  • does not contribute a segment to the URL path
  • does contribute to the file hierarchy for layout/hook/guard discovery (and future hierarchical route features)

So:

  • routes/(reserved)/projects.ts maps to /projects
  • routes/[namespace]/(reserved)/settings.ts maps to /:namespace/settings

2) Layout scoping works naturally

A +layout.ts inside a group applies only to routes within that group subtree.

Example:

routes/
  (reserved)/
    +layout.ts
    projects.ts
    namespaces.ts
 
  [namespace]/
    (reserved)/
      +layout.ts
      index.ts
      settings.ts
      projects.ts
  • /projects and /namespaces share routes/(reserved)/+layout.ts
  • /:namespace routes under [namespace]/(reserved) share a different layout
  • other siblings at the same level are unaffected

3) Hard error on collisions (required)

Because groups are "virtual", it's possible to accidentally create two routes that resolve to the same URL:

  • routes/(a)/projects.ts/projects
  • routes/(b)/projects.ts/projects

This proposal makes collisions a hard error at route compilation/startup:

  • fail fast with a clear error message showing:
    • the resulting URL
    • all file paths that map to it

This aligns with the view that collisions don't represent a useful feature in practice and should be rejected deterministically.

4) Syntax choice: (NAME) is the feature

Group names can be arbitrary identifiers (recommend: a-zA-Z0-9_-), but the name is semantic only (used for readability and diagnostics), not routing.

Security

No new security surface is introduced; this is a routing/layout organisational feature.

However, to avoid subtle routing mistakes:

  • collisions must hard-error (no precedence rules, no "first match wins")
  • error messages should be explicit to prevent accidentally "hiding" routes behind groups

Summary of changes

Area Change
Router file mapping Ignore (group) directories when constructing URL paths
Layout resolution Treat (group) as a normal directory for layout scoping/inheritance
Diagnostics Detect and hard-error on any URL collisions created by grouping
Documentation Add "Virtual route groups" section with examples + collision rules
Tests Add route mapping tests for grouped + nested grouped routes

Open questions

  • Should (group) directories be allowed anywhere (including nested inside dynamic segments like [namespace])? (This proposal assumes yes.)
  • Should group names be restricted to a safe character set (e.g. [A-Za-z0-9_-]+) for portability and clear error messages?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions