In Vue Router 5, a catalogue has /products/new, /products/:id(\d+) and /products/:slug; how does the router pick a route, and when does array order matter?
answer
- score, not array order
- static beats dynamic
- custom regex earns a bonus
- optional, repeatable, wildcard lose points
- ties go to the first declared
basics
~10 sVue Router ranks records by a per-segment score: static segments beat params, a custom regex beats a plain param, and optional, repeatable and wildcard params score lower. Array order only breaks exact ties.
solid answer
~40 sVue Router 4 and later do not try routes in array order. Each path gets a score per segment: static text scores highest, a param with a custom regex like `(\d+)` scores above a plain param, and `?`, `+`, `*` and `.*` subtract points. The router sorts records by score and takes the first whose regex matches. So `/products/new` wins over the params, `/products/42` goes to `:id(\d+)`, and `/products/blue-kettle` fails the digit regex and falls to `:slug`, whatever their order in the array. Array order matters only on an exact tie, where the record added first wins: `/products/:id` and `/products/:slug` both unconstrained makes the second unreachable. `strict` and `sensitive` add tiny bonuses that only break ties. In a JavaScript string the regex backslash must be escaped.
code
ts · 9 linesconst routes = [
// array order is irrelevant here: ranking decides
{ path: '/products/:slug', component: () => import('@/views/ProductBySlug.vue') },
{ path: '/products/:id(\\d+)', component: () => import('@/views/ProductById.vue') },
{ path: '/products/new', component: () => import('@/views/NewProduct.vue') },
]
// /products/new -> NewProduct (static segment)
// /products/42 -> ProductById (custom regex beats plain param)
// /products/blue-kettle -> ProductBySluggo deeper
Know that /products/new wins over /products/:id without reordering the routes array.
Explain the per-segment score, the custom regex bonus, and the penalties for optional, repeatable and wildcard params.
Diagnose unreachable routes caused by ties, fix them by changing shapes rather than order, and keep custom regexes simple and fast.
Decide how much validation belongs in URL patterns versus the data layer across a large route table.
## Ranking replaces array order Vue Router 3 matched routes in the order they appeared. Vue Router 4 introduced its own path parser, and Vue Router 5 keeps it: each record's path is turned into a regular expression **and a score**, the records are kept sorted by score, and a URL goes to the first record in that sorted list whose regex matches. The consequence interviewers look for: **you do not need to order routes carefully**, except in one case. ## How a path is scored The score is a list with one entry per segment, compared segment by segment from the left. Within a segment, the kind of token decides the value: | Segment token | Relative score | |---|---| | static text, `/new` | highest | | param with a custom regex, `/:id(\d+)` | high | | plain param, `/:slug` | medium | | optional param, `/:slug?` | lower | | repeatable param, `/:path+` or `/:path*` | lower still | | wildcard regex, `/:rest(.*)` | lowest | The root `/` has its own high score. Because the comparison goes left to right, `/products/...` records are first ordered by their shared static `products` segment, then by the second segment. ## The catalogue example ```ts const routes = [ { path: '/products/:slug', component: ProductBySlug }, { path: '/products/:id(\\d+)', component: ProductById }, { path: '/products/new', component: NewProduct }, ] ``` 1. `/products/new`: all three regexes except the digit one match, and the static segment scores highest, so `NewProduct` renders even though it is last in the array. 2. `/products/42`: the digit regex matches and outranks the plain param, so `ProductById` renders. 3. `/products/blue-kettle`: only `:slug` matches, so `ProductBySlug` renders. Notice the backslash: in a JavaScript string, `\d` must be written `\\d` in source, or the regex receives a plain `d`. A `)` inside the custom regex must also be escaped, since `)` ends it. ## When array order still matters On an **exact tie** the router keeps the record that was added first. Two unconstrained params in the same position, `/products/:id` and `/products/:slug`, score the same; every URL matches both, so the first one declared wins and the second is **unreachable**. The fix is never to reorder but to make them different: a static prefix (`/p/:slug`), a custom regex on one of them, or a single record that decides in the component. ## A tie-break checklist When two records seem to fight over a URL, walk them left to right, segment by segment: 1. Is one segment static where the other has a param? The static one wins. 2. Do both have params? The one with a custom regex wins, if its regex matches. 3. Is one param optional, repeatable or a wildcard? It loses to a plain required param. 4. Still equal? The record added first wins, and the other is dead for those URLs. ## `strict` and `sensitive` - Both default to `false`: paths are case-insensitive and a trailing slash is optional. - `strict: true` rejects a trailing slash when the path has none; `sensitive: true` makes matching case-sensitive. - They can be set on the router, for all records, or on a single record. - Each adds a **tiny** score bonus, deliberately smaller than any token difference, so they only decide between otherwise equal records. ## Debugging a surprising match - Remember that the **sorted** list is what matters; print `router.getRoutes()` to see the records the router holds, then reason about scores. - Check custom regexes for escaping mistakes first; a lost backslash is the most common cause of a route that never matches. - The router docs link a path ranker tool that shows the generated regex and score for a set of paths, which settles most arguments. - Beware slow patterns: a `.*` wildcard followed by a repeat modifier and more path, like `/:rest(.*)*/edit`, produces a very slow regex. ## Trade-offs of custom regexes A regex makes the table self-sorting and lets unknown shapes fall through to a 404, but it also moves validation into the URL layer: `/products/0042` matches `\d+` and still needs a lookup. Keep regexes simple (digits, a fixed set of words), and let the component or data layer handle everything else.
- A teammate adds /products/:category after /products/:slug and it never matches. How do you explain and fix it?Both are unconstrained single params in the same position, so they tie and the one added first wins every time. Reordering only swaps which one is dead. Give them different shapes: a static prefix such as `/categories/:category`, or a regex constraining one of them to known values.
- Why is an optional param scored lower than a required one?An optional param matches more URLs, including the one without the segment, so it is less specific. Scoring it lower means `/users/:id` wins over `/users/:id?` for `/users/7`, while the optional record still catches `/users`. The same logic puts repeatable params and wildcards below both.
saying these in an interview costs you the question
- Routes are tried in array order, so put specific ones first.
- Moving /products/new above /products/:id is required for it to match.
- Two plain params in the same position are both reachable depending on the URL.
- strict: true can outrank a static segment.
- Writing '\d+' with a single backslash in a JS string works fine.