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?
Motivation
Sometimes it's useful to group routes without creating a URL segment, mainly to:
Example intent:
routes/(reserved)/projects.tsroutes/(reserved)/namespaces.tsroutes/(reserved)/+layout.ts…but not for everything in
routes/.And similarly under a dynamic segment:
routes/[namespace]/(reserved)/index.tsroutes/[namespace]/(reserved)/settings.tsroutes/[namespace]/(reserved)/projects.tsroutes/[namespace]/(reserved)/+layout.tsYou 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:
So:
routes/(reserved)/projects.tsmaps to/projectsroutes/[namespace]/(reserved)/settings.tsmaps to/:namespace/settings2) Layout scoping works naturally
A
+layout.tsinside a group applies only to routes within that group subtree.Example:
/projectsand/namespacesshareroutes/(reserved)/+layout.ts/:namespaceroutes under[namespace]/(reserved)share a different layout3) 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→/projectsroutes/(b)/projects.ts→/projectsThis proposal makes collisions a hard error at route compilation/startup:
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 featureGroup 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:
Summary of changes
(group)directories when constructing URL paths(group)as a normal directory for layout scoping/inheritanceOpen questions
(group)directories be allowed anywhere (including nested inside dynamic segments like[namespace])? (This proposal assumes yes.)[A-Za-z0-9_-]+) for portability and clear error messages?