If you render dynamic blocks from a CMS or configuration file, you often end up with a component map like this:
const componentMap = computed(() => ({
userMenu: {
component: UserMenu,
props: {
user: user.value
},
},
})
The problem is that TypeScript cannot infer the prop types here.
Why TypeScript cannot help without a helper
When you write a plain object literal, TypeScript sees each key independently and has no way to link component to props within the same entry.
The props value is widened to whatever object you pass — TypeScript does not look up the resolved props type of component and validate props against it. This means you can pass wrong prop names, wrong types, or forget required props, and TypeScript stays silent.
A generic helper solves this by capturing the relationship at the call site.
The entry helper
Vue's Component<T> type, available via import type { Component } from 'vue', accepts the props type as its first generic argument. You can use this to tie the component and its props together in a single call:
<script setup lang="ts">
import { computed } from 'vue'
import type { Component } from 'vue'
import UserComponent from './UserComponent.vue'
const entry = <T>(component: Component<T>, props: T) => ({ component, props })
const componentMap = computed(() => ({
userMenu: entry({
component: UserMenu,
props: {
user: user.value
},
}),
})
const componentList = ['userMenu'] as const
</script>
<template>
<component v-for="componentId of componentList" :key="componentId"
:is="componentMap[componentId].component"
v-bind="componentMap[componentId].props"
/>
</template>
The generic T is inferred from the component you pass to entry(). TypeScript then checks that props satisfies T at that same call site.
If you rename a prop, remove one, or pass a wrong type, TypeScript reports it immediately — before the app ever runs.
When this pattern is useful
- CMS-driven pages where block types are defined in configuration and rendered dynamically.
- Design systems that map token names to presentational components.
- Any place where the list of components is owned externally (e.g. a plugin, a feature flag config) and the consumer just renders whatever arrives.
If you liked this Vue tip, follow me on Twitter to get notified about new tips, blog posts, and more. Alternatively (or additionally), you can subscribe to my weekly Vue & Nuxt newsletter :

