Nico Prat

Nico Prat

Typing Vue Router pages props

One step closer to full routing type safety in Vue


paths in forest symbolizing routes in our app

We are progressively adding TypeScript in our Vue application since a couple years ago, and recently migrating to Vue 3 greatly improved our developer experience. There are still some holes left though, notably in the routing part, and there is not always an easy way to fix them.

Let's say you have a simple route that loads a page component, which needs a category prop:

<script lang="ts" setup>
// pages/Category.vue

defineProps({
  category: {
    type: String,
    required: true,
  }
});
</script>
// routes.ts
const routes = [
  {
    path: '/blog/:category,
    name: 'Category',
    component: () => import('@/pages/Category.vue'),
    // no type safety here
    props: ({ params }) => ({ category: params.category })
  }
]

By default, TypeScript can't help us make sure we pass the correct props to the component.

Exporting props to force type safety#

The simplest solution is to expose the props definition as a simple object, so we can check against them:

<script lang="ts" setup>
// pages/Category.vue
defineProps(props);
</script>

<script lang="ts">
// Needs a separate script tag without `setup` sugar
const props = {
  category: {
    type: String,
    required: true,
  }
};

// Exporting a type on its own allows us to prevent loading the whole component in the routes definition
export type Props = typeof props
</script>
// routes.ts
import type { Props } from '@/pages/Category.vue'

const routes = [
  {
    path: '/blog/:category',
    name: 'Category',
    component: () => import('@/pages/Category.vue'),
    // type safety is now ensured
    props: ({ params }) => ({ mode: params.mode } as Props) 
  }
]

This works pretty well for the easiest cases, but we usually have stricter types that use Vue PropTypes utility, like this:

const props = {
  category: {
    type: String as PropType<'development' | 'design'>,
    required: true,
  }
};

ExtractPropTypes to the rescue#

Fortunately, Vue exposes a TypeScript utility that can extract the prop types to narrow our props type:

// routes.ts
import type { ExtractPropTypes } from 'vue';
import type { Props } from '@/pages/Category.vue'

const routes = [
  {
    path: '/blog/:category',
    name: 'Category',
    component: () => import('@/pages/Category.vue'),
    // type safety is now ensured
    props: ({ params }) => ({ category: params.category } as ExtractPropTypes<Props>) 
  }
]

Always keep in mind that this does not check the real value at runtime, so a prop validator might still be necessary.

In action#

You can have a look at this example in the Vue SFC Playground:

In the future#

We might be able to extract prop types even more easily if this pull request proposing more TypeScript utilities is merged. For instance, with ExtractComponentProp we could remove the annoying step of exporting the props as a plain object completely:

const options = {
 props: { foo: String },
 setup(){
   // ...
 }
}

const Comp = defineComponent(options)
expectType<ExtractComponentProp<typeof Comp>>(options.props)

Let's see what cool features the future brings to keep our code even safer!