A new Router to guide your apps

London 4th Oct 2019

GitHub icon
Twitter icon

Eduardo

San Martin Morote

🌍 Vue core team

👨‍💻 Freelance

📍Paris

GitHub icon

Routing in you App

the 1st week

Routing in your App

after a year

SPA
Routers

Three kind of routers

  • Imperative
  • Declarative
  • Configuration-based

Imperative

Page.js

✅ Programmatic navigation

❌ Declarative navigation

Navigation Guards

❌ Dynamic routing (add/remove routes)

❌ Declarative routing

Dynamic routing

router.addRoute('/some-route', options)
router.removeRoute('/some-route')

⚠️ not actual API

Reach Router

<Router>
  <Home path="/" />
  <UserProfileEdit path="/users/:id/edit" />
</Router>
<div>
  <Link to="/">Home</Link>
  <Link to={`/users/${this.user.id}/edit`} />
</div>

Reach router / React Router

Declarative

✅ Programmatic navigation

 Declarative navigation

 Navigation Guards

Dynamic routing

 Declarative routing

 

Vue Router

const router = new Router({
  mode: 'history',
  routes: [
    { path: '/', component: Home },
    { path: '/users/:id', component: UserProfile },
  ]
})
<div>
  <router-link to="/">Home</router-link>
  <router-link :to="`/users/${this.user.id}`">
    My Profile
  </router-link>
</div>

Vue router

Configuration based

 Programmatic navigation

 Declarative navigation

Navigation Guards

⚠️ Dynamic routing (add/remove routes)

 Declarative routing

🗂 HISTORY

🚦 ROUTER

📦 COMPONENTS

📦 COMPONENTES

<router-view/>
<router-link to="/">Home</router-link>

🚦 ROUTER

  • Route matching​

    • ​match()
    • ​resolve()
  • Navigation

    • ​​currentRoute
    • push() replace(), ...
    • ​beforeEach(), ...
  • Creating routes

    • new Router({ routes })
    • addRoutes()

🗂 HISTORy

  • JS ↔ URL

    • push() replace(), ...
    • listen()

The
Good PARTS

📦 router-view
<router-view/>
  • Dynamically render current view
  • Pass params as props
$route
📦 router-link
  • Resolve target location
  • Render an anchor tag with link
  • Handles click event
  • Applies active classes
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'User', params: {id: '2'}}">...</router-link>
$route
$router

Normalized Route

{
  path: '/users/2',
  fullPath: '/users/2?q=foo'
  name: 'UserProfile',
  query: { q: foo },
  hash: '',
  params: { id: '2' },
  meta: {}
}

Navigation
Guards

Global Guards

router.beforeEach((to, from, next) => {
  // verify roles based on `to`
  // ...

  // call `next` *once*
  if (!isLoggedIn) next('/login') // redirect to login
  else if (!isAuthorized) next(false) // abort
  else next() // allow navigation
})

Per-route Guards

{
  path: '/admin',
  component: AdminPanel,
  beforeEnter (to, from, next) {
    if (isAdmin) next()
    else next(false)
  },
}

in-component guards

export default {
  name: 'AdminPanel',

  data: () => ({ adminInfo: null }),

  async beforeRouteEnter (to, from, next) {
    const adminInfo = await getAdminInfo()
    next(vm => {
      vm.adminInfo = adminInfo
    })
  },
}

beforeEach

beforeEnter

beforeRouteEnter

next()
next(false)

/posts ➡️ /admin

/posts

Admin.vue

beforeEnter

beforeRouteEnter

next()

/posts ➡️ /admin

Fetch Admin Component

The
Sad PARTS

No clear Division of responsibilities

History

Router

router.beforeEach
function beforeEach(guard: NavigationGuard): ListenerRemover {
  this.beforeGuards.push(guard)
  return () => {
    const i = this.beforeGuards.indexOf(guard)
    if (i > -1) this.beforeGuards.splice(i, 1)
  }
}
router.push
function push (location, onComplete, onAbort) {
  this.history.push(location, onComplete, onAbort)
}

No clear Division of responsibilities

  • Harder to contribute
  • Harder to extend
beforeRouteEnter
<template>
  <p>Hello {{ user.name }}!</p>
</template>

<script>
  export default {
    data: () => ({ user: null }),
    async beforeRouteEnter(to, from, next) {
      const user = await fetchCurrentUser()
      next(vm => {
        vm.user = user
      })
    }
  }
</script>

Oops!

After mounted

Differences between guards

beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave

❌ No access to the component instance (this)

Can pass a callback

Access to the component instance (this)

❌ Callback is ignored

Save
Vue
Router

🗂 HISTORy

  • Store visited URLs

  • JS ↔ URL

    • push() replace(), ...
    • listen()

🚦 ROUTER

  • Route matching

    • ​match()
    • ​resolve()
  • Navigation

    • ​​currentRoute
    • push() replace(), ...
    • ​beforeEach(), ...
  • Creating routes

    • new Router({ routes })
    • addRoutes()

🗂 HISTORy

  • push() replace()
  • listen()
  • URL parsing

API

  • Modify the Location
  • Parses URL
    • path
    • query
    • hash
  • Notifies when Location changes
  • Handles Encoding problems
  • Can be overloaded

Responsibilities / Expectations

🚦 ROUTER

  • Route matching

    • ​resolve()
  • Adding Route Records
    • addRouteRecord
    • removeRouteRecord
  • Navigation

    • ​​currentRoute
    • push() replace(), ...
  • Navigation Guards

    • ​beforeEach(), ...
  • Dynamic Routing
    • ​addRoute / removeRoute

🚦 ROUTER

🛣 Matcher

🛣 Matcher

  • Route matching

    • ​resolve()
  • Adding Route Records
    • addRouteRecord
    • removeRouteRecord

API

  • Resolving a Router Location to a Route Record
  • Only handles the path of URL
  • Handles priority of Route Records
  • Parses/handle params

Responsibilities / Expectations

  • Navigation

    • ​​currentRoute
    • push(), replace()
  • Navigation Guards

    • ​beforeEach(), ...
  • Dynamic Routing
  • Lazy loading Pages
  • Error handlers

🚦 ROUTER

API

  • Async navigation
  • Trigger Navigation guards
  • Expose current Route Location
  • Handle redirections
  • Dynamic Routing

Responsibilities / Expectations

Cool
new
Vue
Router

The Future is in Typescript

export type RouteRecord =
  | RouteRecordSingleView
  | RouteRecordMultipleViews
  | RouteRecordRedirect
  • Easier to contribute
  • Less regressions
  • Improved discoverability
<router-link
  to="/about"
  v-slot="{ href, route, navigate, isActive }"
>
  <NavLink
    :active="isActive"
    :href="href"
    @click="navigate"
  >{{ route.fullPath }}</NavLink>
</router-link>

router-link scoped slot

Navigation Failures

  • Navigating to the current location
  • Cancelling navigation in a guard
    • Redirecting
    • Aborting
  • Unhandled Errors
try {
  await router.push('/somewhere')
} catch (err) {
  console.log(
    'Did not make it to ' + err.to.fullPath
  )
}

Navigation Failures

Complete Stacktraces

Complete Stacktraces

function createHistory(): RouterHistory {
  const base = createAbstractHistory()
  const navigationStack = []
  
  // overload push/replace methods to write the stack
  function push() {}
  function replace() {}
  
  return {
    ...base,
    
    push,
    replace,
    navigationStack,
  }
}

Custom History

new Router({
  mode: createHistory(),
})

Custom History

new Router({
  routes: [
    { path: '/(.*)', name: 'NotFound' },
    { path: '/home', name: 'Home' }
  ]
})

Path Ranking

paths.esm.dev
router.addRoute({ name: 'User', path: '/users/:id' })
router.addRoute({ name: 'UserById', path: '/users/:id(\d+)' })
router.addRoute({ name: 'Myself', path: '/users/me' })

router.removeRoute({ name: 'TemporaryRoute' })

router.resolve('/users/me') // -> { name: 'Myself' }
router.resolve('/users/230') // -> { name: 'UserById' }
router.resolve('/users/posva') // -> { name: 'User' }

Dynamic Routing

secret
routes

if (loggedIn) router.addRoutes()
import { useLink } from 'vue-router'

function useConfirmLink(to, message) {
  const link = useLink(to)
  
  function navigate() {
    if (window.confirm(message)) return link.navigate()
  }
  
  return { ...link, navigate }
}

Composition API

Composition API

​useLink
useLocation
onBeforeRouteLeave
onBeforeRouteUpdate

More Tests

  • Full unit test coverage
  • Automated Cross Browser E2E tests

RFCs

  • Scoped Slot API for router-link
  • Better active matching
  • a11y improvements
  • More navigation information

Vue 2 & 3

  • Support both versions

Vue Router

Vue Router

Vue 2

Vue 3

Focus on supporting Vue 3

Smaller improvements for Vue 2

  • Sponsors 🙌

esm.dev/sponsor

Thanks! 🖖

  • GitHub icon
GitHub icon

A new Router to guide your apps

By Eduardo San Martin Morote

A new Router to guide your apps

Vue official router has been a pleasure to build apps with but as the community grows, so do the router needs. However, it hasn't kept up as good as I wish it had with all the good proposals out there. As a result, we have also taken more time to work on the new version of Vue router and shaping an API that will serve Vue 2 and Vue 3. A more extensible router, easier to contribute and new architecture from scratch. Let's talk about what has changed in the new Router and all the improvements we are bringing.

  • 623

More from Eduardo San Martin Morote