---
title: "Vue Router 4.6: what's new · vuejs.school"
description: "Vue Router 4.6 rebuilds the package, fixes RouterLink under strictTemplates and iOS scroll restoration, and previews a smaller, faster route matcher."
canonical_url: "https://vuejs.school/community/blog/vue-router-4-6-whats-new"
meta:
  "og:description": "Vue Router 4.6 rebuilds the package, fixes RouterLink under strictTemplates and iOS scroll restoration, and previews a smaller, faster route matcher."
  "og:image": "https://vuejs.school/_og/d/a_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXcgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXc%2Cdescription_~VnVlIFJvdXRlciA0LjYgcmVidWlsZHMgdGhlIHBhY2thZ2UsIGZpeGVzIFJvdXRlckxpbmsgdW5kZXIgc3RyaWN0VGVtcGxhdGVzIGFuZCBpT1Mgc2Nyb2xsIHJlc3RvcmF0aW9uLCBhbmQgcHJldmlld3MgYSBzbWFsbGVyLCBmYXN0ZXIgcm91dGUgbWF0Y2hlci4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTQtNi13aGF0cy1uZXci%2Cs_Ugh3mv02QK8EsT7M.png"
  "og:image:alt": "Vue Router 4.6: what's new · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Vue Router 4.6: what's new · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXcgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXc%2Cdescription_~VnVlIFJvdXRlciA0LjYgcmVidWlsZHMgdGhlIHBhY2thZ2UsIGZpeGVzIFJvdXRlckxpbmsgdW5kZXIgc3RyaWN0VGVtcGxhdGVzIGFuZCBpT1Mgc2Nyb2xsIHJlc3RvcmF0aW9uLCBhbmQgcHJldmlld3MgYSBzbWFsbGVyLCBmYXN0ZXIgcm91dGUgbWF0Y2hlci4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTQtNi13aGF0cy1uZXci%2Cs_Ugh3mv02QK8EsT7M.png"
  "twitter:image:alt": "Vue Router 4.6: what's new · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## What shipped in 4.6.0

The changelog entry for 4.6.0 lists:

- A rebuilt package. The notes say the build moved to `tsup`, though the `build` script in the published 4.6.0 `package.json` runs `tsdown`. They ask for reports of problems with types or exports that do not resolve.
- A new `vue-router/experimental` export with custom resolvers, still undocumented (previewed below).
- A fix for `RouterLink` under `strictTemplates`: event handlers on the component no longer produce a type error.
- A fix for scroll restoration on iOS, which no longer relies on `beforeunload`.
- A types fix for options missing from `RouteLocationGeneric`.
- Minor performance work in `parseURL`.

## Event handlers on `RouterLink` pass strict template checks

This fix is for projects that turn on strict template checking. The option goes under `vueCompilerOptions` in the tsconfig that includes your `.vue` files, which is `tsconfig.app.json` in a create-vue project:

*tsconfig.app.json*

```
{
  "vueCompilerOptions": {
    "strictTemplates": true
  }
}
```

With it, `vue-tsc` rejects any prop or event that a component does not declare. That catches a misspelt prop such as `:lable="..."`, which Vue would otherwise pass through silently as an attribute.

Before 4.6, the type of `RouterLink` declared only its own props (`to`, `replace` and the rest), so a handler on it failed the type check even though it worked in the browser. On 4.5, `vue-tsc` rejects the `@mouseenter` in this component, saying that `onMouseenter` does not exist in the type of `RouterLink`'s props:

*src/components/NavItem.vue*

```
<script setup lang="ts">
import { RouterLink } from 'vue-router'

const props = defineProps<{ to: string; label: string }>()

function track() {
  console.log(`hovered ${props.label}`)
}
</script>

<template>
  <RouterLink :to="props.to" @mouseenter="track">{{ props.label }}</RouterLink>
</template>
```

4.6 adds the attributes and events of the `<a>` element to the type of `RouterLink`, except `href`, which the link builds from `to`. The file above type-checks on 4.6. If you had turned `strictTemplates` off because of this error, you can turn it back on.

## Scroll position survives a refresh on iOS

When a page is reloaded, Vue Router saves the scroll position just before the page unloads, so that it can restore it afterwards. That saved position is what `scrollBehavior` receives as `savedPosition` after a refresh. Up to 4.5, the router saved it in a `beforeunload` listener, and iOS Safari does not fire `beforeunload`. So on an iPhone, a refresh went back to the top of the page.

4.6 saves the position on `pagehide`, and on `visibilitychange` when the page becomes hidden, events that iOS Safari does fire. A refresh there now lands where the person was, with no change to your `scrollBehavior`.

## Install 4.6.3: what the patches fixed

4.6.0 had three regressions. Two came from the new build, which left out files that some setups load directly; the third came from the `parseURL` work. Each got its own patch:

- **4.6.1** (15 October) put back `dist/vue-router.esm-bundler.js` for wrappers that import that file by its path; the bug report came from a uni-app package. The file now prints a deprecation warning, and the next major removes it.
- **4.6.2** (15 October) stopped passing a leading `?` to a custom `parseQuery`. On 4.6.0, a `parseQuery` function given to `createRouter()` received `?foo=bar` instead of `foo=bar`.
- **4.6.3** (16 October) restored `vue-router.esm-browser.js` and its minified twin, `vue-router.esm-browser.prod.js`. Pages that load the router from a CDN as an ES module use these files: their `process.env` references are removed, so they run without a bundler.

Unless you pass your own `parseQuery`, none of this touches a Vite project that imports `vue-router` normally, but it is the reason to install 4.6.3 and skip the three before it.

## How to upgrade

One command, then check the version that lands:

```
npm install vue-router@4
npm ls vue-router
```

The second line should print 4.6.3 or later. The 4.6 releases declare Vue `^3.5.0` as their peer dependency, where 4.5 accepted anything from Vue 3.2, so a project still on Vue 3.4 should update Vue first. `unplugin-vue-router` 0.16.0, published the same day as 4.6.0, requires 4.6 in turn.

The notes list no breaking changes. The routes array, `useRoute()`, `useRouter()`, navigation guards, `scrollBehavior` and typed routes through `unplugin-vue-router` all work as they did. On Vue 3.5, upgrade now: there is nothing to migrate. If you load the router from a CDN, use a wrapper that imports its dist files, or pass a custom `parseQuery`, test once on 4.6.3 before you deploy: those are the paths the patches touched.

## A preview of the new route matcher

The stable router turns your routes array into a list of matchers. Every navigation walks that list, tests the path against each route's regular expression and extracts params as strings. That layer is well tested, and it supports aliases, routes added at runtime, nested children and named views.

The custom resolvers in `vue-router/experimental` are a rewrite of that layer. The notes name four gains:

- **Custom param types.** An `id` can be parsed as an integer by the matcher, rather than by every component that reads it.
- **Matching on query parameters**, not only on the path.
- **A smaller router runtime.** The experimental matcher does not parse path strings such as `/orders/:id` itself. It takes ready-made regular expressions and param parsers, which a build tool can generate. The notes say it is meant to be used with `unplugin-vue-router`, the plugin that generates routes from your `pages/` folder.
- **Faster route matching.** The notes give no numbers.

The notes point to an [experimental playground](https://github.com/posva/unplugin-vue-router/tree/main/playground-experimental) in the `unplugin-vue-router` repository for anyone who wants to look. If you use file-based routing, the notes say the switch will eventually be transparent. If you write routes by hand, nothing changes until the resolver stops being experimental and is documented.

## How params are matched today

Custom param types change how params are read, so it helps to be precise about how they work in the stable router, which is what you write today. A param is a path segment that starts with a colon. By default it matches anything up to the next slash and arrives in `route.params` as a string. You can narrow it with a regular expression in parentheses, mark it optional with `?`, or let it repeat with `+` or `*`. This router file uses all three:

*src/router/index.ts*

```
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // /orders/42 matches, /orders/abc does not, so it can fall through to the next route
    { path: '/orders/:id(\\d+)', component: () => import('../pages/OrderPage.vue') },
    // /orders/abc shows a search for that text instead of a 404
    { path: '/orders/:query', component: () => import('../pages/OrderSearchPage.vue') },
    // /users and /users/7 both match; userId is '' when absent
    { path: '/users/:userId?', component: () => import('../pages/UsersPage.vue') },
    // /docs/guide/install matches with chapters = ['guide', 'install']
    { path: '/docs/:chapters+', component: () => import('../pages/DocsPage.vue') },
  ],
})

export default router
```

The backslash is doubled because the pattern lives inside a JavaScript string: `\\d` reaches the router as `\d`. Repeatable params arrive as an array, and when you navigate to such a route by name you pass an array too.

Two router options change the defaults: `strict: true` makes a trailing slash significant, and `sensitive: true` makes matching case-sensitive. Set them on `createRouter()` for every route, or on a single route record.

The regular expression guarantees the shape of the string, but a param is still a string. The component converts it:

*src/pages/OrderPage.vue*

```
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const orderId = computed(() => Number(route.params.id))
</script>

<template>
  <h1>Order #{{ orderId }}</h1>
</template>
```

Every component that reads `id` repeats that `Number()` call, and nothing stops a route elsewhere from treating the same param as text. Custom param types are meant to remove that duplication: in the experimental resolver the route parses the param, and the component receives a number. Until then, the conversion is yours, and the regular expression plus a `computed()` works on every 4.x release.

**Check it stuck**3 questions from the Dynamic routes and params quiz.

[**Practice now**](https://vuejs.school/learn/routing/dynamic-routes-and-params?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Tutorial · 6 min read**Dynamic routes, params and typed routes**](https://vuejs.school/community/blog/dynamic-routes-params-and-typed-routes) [Tutorial · 5 min read**Migrating from unplugin-vue-router to Vue Router 5**](https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/vue-router-4-6-whats-new#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-10-21","description":"Vue Router 4.6 rebuilds the package, fixes RouterLink under strictTemplates and iOS scroll restoration, and previews a smaller, faster route matcher.","name":"Vue Router 4.6: what's new","url":"https://vuejs.school/community/blog/vue-router-4-6-whats-new","breadcrumb":{"@id":"https://vuejs.school/community/blog/vue-router-4-6-whats-new#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/vue-router-4-6-whats-new"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/vue-router-4-6-whats-new#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-10-21","description":"Vue Router 4.6 rebuilds the package, fixes RouterLink under strictTemplates and iOS scroll restoration, and previews a smaller, faster route matcher.","headline":"Vue Router 4.6: what's new","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXcgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXc%2Cdescription_~VnVlIFJvdXRlciA0LjYgcmVidWlsZHMgdGhlIHBhY2thZ2UsIGZpeGVzIFJvdXRlckxpbmsgdW5kZXIgc3RyaWN0VGVtcGxhdGVzIGFuZCBpT1Mgc2Nyb2xsIHJlc3RvcmF0aW9uLCBhbmQgcHJldmlld3MgYSBzbWFsbGVyLCBmYXN0ZXIgcm91dGUgbWF0Y2hlci4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTQtNi13aGF0cy1uZXci%2Cs_Ugh3mv02QK8EsT7M.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/vue-router-4-6-whats-new#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/vue-router-4-6-whats-new#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/vue-router-4-6-whats-new#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Vue Router 4.6: what's new","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXcgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXc%2Cdescription_~VnVlIFJvdXRlciA0LjYgcmVidWlsZHMgdGhlIHBhY2thZ2UsIGZpeGVzIFJvdXRlckxpbmsgdW5kZXIgc3RyaWN0VGVtcGxhdGVzIGFuZCBpT1Mgc2Nyb2xsIHJlc3RvcmF0aW9uLCBhbmQgcHJldmlld3MgYSBzbWFsbGVyLCBmYXN0ZXIgcm91dGUgbWF0Y2hlci4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTQtNi13aGF0cy1uZXci%2Cs_Ugh3mv02QK8EsT7M.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXcgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA0LjY6IHdoYXQncyBuZXc%2Cdescription_~VnVlIFJvdXRlciA0LjYgcmVidWlsZHMgdGhlIHBhY2thZ2UsIGZpeGVzIFJvdXRlckxpbmsgdW5kZXIgc3RyaWN0VGVtcGxhdGVzIGFuZCBpT1Mgc2Nyb2xsIHJlc3RvcmF0aW9uLCBhbmQgcHJldmlld3MgYSBzbWFsbGVyLCBmYXN0ZXIgcm91dGUgbWF0Y2hlci4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTQtNi13aGF0cy1uZXci%2Cs_Ugh3mv02QK8EsT7M.png"}]}
```