RouteMeta Interface | Vue Router
RouteMeta is an interface that defines the structure for custom metadata associated with route records. It allows you to attach arbitrary data to routes for various purposes like authentication, layout configuration, and feature flags. 🏷️
📋 Interface Definition
interface RouteMeta {
// Custom properties can be added here
[key: string]: any
}2
3
4
🎯 Purpose and Usage
The RouteMeta interface provides a type-safe way to attach custom metadata to your route configurations. This metadata can be used for route guards, layout selection, breadcrumb generation, and other route-specific behaviors.
Key Use Cases:
- ✅ Authentication - Mark routes that require login
- ✅ Authorization - Specify required permissions or roles
- ✅ Layout Configuration - Choose different layouts for different routes
- ✅ SEO Optimization - Add meta tags and page titles
- ✅ Feature Flags - Enable/disable features per route
🔍 Property Structure
Extensible Interface
The RouteMeta interface is designed to be extended with your application-specific properties. By default, it accepts any string-keyed properties.
// Basic usage with custom properties
const routeMeta: RouteMeta = {
requiresAuth: true,
pageTitle: "User Profile",
breadcrumb: "Profile"
}2
3
4
5
6
Type-Safe Extension
For better TypeScript support, you can extend the interface with your specific properties:
// Extend RouteMeta in your application
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean
requiredRoles?: string[]
pageTitle?: string
breadcrumb?: string
layout?: 'default' | 'admin' | 'auth'
noFooter?: boolean
// Add your custom properties here
}
}2
3
4
5
6
7
8
9
10
11
12
💡 Practical Usage Examples
Basic Route Configuration
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
component: Home,
meta: {
pageTitle: 'Home Page',
requiresAuth: false
}
},
{
path: '/admin',
component: AdminDashboard,
meta: {
requiresAuth: true,
requiredRoles: ['admin'],
layout: 'admin',
pageTitle: 'Admin Dashboard'
}
},
{
path: '/user/:id',
component: UserProfile,
meta: {
requiresAuth: true,
breadcrumb: 'User Profile',
pageTitle: (route) => `Profile - ${route.params.id}`
}
}
]2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Navigation Guard with Meta
// Authentication guard
router.beforeEach((to, from) => {
// Check if route requires authentication
if (to.meta.requiresAuth && !isAuthenticated()) {
// Redirect to login
return {
path: '/login',
query: { redirect: to.fullPath }
}
}
// Check role-based access
const requiredRoles = to.meta.requiredRoles as string[] | undefined
if (requiredRoles && requiredRoles.length > 0) {
const userRoles = getCurrentUserRoles()
const hasRequiredRole = requiredRoles.some(role =>
userRoles.includes(role)
)
if (!hasRequiredRole) {
// Show access denied
return '/access-denied'
}
}
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Dynamic Page Titles
// Set page title based on route meta
router.afterEach((to) => {
let pageTitle = 'My App'
if (to.meta.pageTitle) {
if (typeof to.meta.pageTitle === 'function') {
pageTitle = to.meta.pageTitle(to)
} else {
pageTitle = to.meta.pageTitle as string
}
}
document.title = pageTitle
})2
3
4
5
6
7
8
9
10
11
12
13
14
🔧 Advanced Patterns
Meta-Based Layout System
class LayoutManager {
private layouts: Map<string, Component> = new Map()
constructor() {
this.setupLayouts()
}
private setupLayouts() {
this.layouts.set('default', DefaultLayout)
this.layouts.set('admin', AdminLayout)
this.layouts.set('auth', AuthLayout)
this.layouts.set('minimal', MinimalLayout)
}
getLayoutForRoute(route: RouteLocationNormalized): Component {
const layoutName = route.meta.layout as string || 'default'
return this.layouts.get(layoutName) || DefaultLayout
}
// App component usage
setupAppLayout() {
const route = useRoute()
const currentLayout = computed(() =>
this.getLayoutForRoute(route)
)
return { currentLayout }
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Breadcrumb Generation
class BreadcrumbService {
generateBreadcrumbs(route: RouteLocationNormalized): Breadcrumb[] {
return route.matched.map((record, index) => {
const isLast = index === route.matched.length - 1
// Get breadcrumb text from meta or fallback
let text: string
if (record.meta?.breadcrumb) {
text = typeof record.meta.breadcrumb === 'function'
? record.meta.breadcrumb(route)
: record.meta.breadcrumb
} else {
text = String(record.name || this.formatPath(record.path))
}
return {
text,
to: isLast ? undefined : this.getRouteForRecord(record, route),
active: isLast
}
})
}
private formatPath(path: string): string {
return path.split('/')
.filter(segment => segment)
.map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))
.join(' > ')
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Feature Flag System
class FeatureFlagManager {
private enabledFeatures: Set<string> = new Set()
constructor() {
this.loadEnabledFeatures()
}
isFeatureEnabled(route: RouteLocationNormalized, feature: string): boolean {
// Check route-specific feature flags
const routeFeatures = route.meta.features as string[] | undefined
if (routeFeatures?.includes(feature)) {
return true
}
// Check global feature flags
return this.enabledFeatures.has(feature)
}
shouldShowComponent(route: RouteLocationNormalized, component: string): boolean {
const hiddenComponents = route.meta.hiddenComponents as string[] | undefined
return !hiddenComponents?.includes(component)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
🚨 Important Considerations
Meta Property Safety
// ✅ Safe: Check property existence and type
if (to.meta.requiresAuth) {
// TypeScript knows requiresAuth is truthy
if (!isAuthenticated()) {
// Handle authentication
}
}
// ❌ Unsafe: Direct access without checking
const roles = to.meta.requiredRoles // Could be undefined
// ✅ Better: Type checking
const roles = to.meta.requiredRoles as string[] | undefined
if (roles && roles.length > 0) {
// Safe to use roles
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Meta Inheritance
// Meta properties are inherited in nested routes
const routes = [
{
path: '/admin',
meta: { requiresAuth: true, layout: 'admin' },
children: [
{
path: 'users',
component: UserManagement
// Inherits requiresAuth and layout from parent
},
{
path: 'settings',
component: AdminSettings,
meta: {
...// Inherits parent meta
requiresAdmin: true // Additional property
}
}
]
}
]2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
🔗 Related APIs
RouteRecordRaw- Route configuration with metaRouteLocationNormalized- Current route with meta- Navigation Guards - Use meta in route protection
📚 Best Practices
Consistent Meta Property Definitions
// Define a standard set of meta properties for your app
interface AppRouteMeta {
// Authentication & Authorization
requiresAuth?: boolean
requiredRoles?: string[]
permissions?: string[]
// SEO & Page Information
pageTitle?: string | ((route: RouteLocationNormalized) => string)
metaDescription?: string
canonicalUrl?: string
// Layout & UI
layout?: string
hideHeader?: boolean
hideFooter?: boolean
fullWidth?: boolean
// Business Logic
features?: string[]
analyticsCategory?: string
breadcrumb?: string | ((route: RouteLocationNormalized) => string)
}
// Extend Vue Router's RouteMeta
declare module 'vue-router' {
interface RouteMeta extends AppRouteMeta {}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Meta Validation
class MetaValidator {
static validateRouteMeta(meta: RouteMeta): ValidationResult {
const errors: string[] = []
// Validate requiresAuth
if (meta.requiresAuth !== undefined && typeof meta.requiresAuth !== 'boolean') {
errors.push('requiresAuth must be a boolean')
}
// Validate requiredRoles
if (meta.requiredRoles && !Array.isArray(meta.requiredRoles)) {
errors.push('requiredRoles must be an array')
}
// Validate pageTitle
if (meta.pageTitle && typeof meta.pageTitle !== 'string' &&
typeof meta.pageTitle !== 'function') {
errors.push('pageTitle must be string or function')
}
return { isValid: errors.length === 0, errors }
}
static validateRouteConfig(route: RouteRecordRaw): ValidationResult {
if (!route.meta) {
return { isValid: true, errors: [] }
}
return this.validateRouteMeta(route.meta)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Meta Utilities
class MetaUtilities {
// Merge meta from parent and child routes
static mergeMeta(parentMeta: RouteMeta | undefined, childMeta: RouteMeta | undefined): RouteMeta {
return {
...parentMeta,
...childMeta,
// Special handling for arrays
requiredRoles: [
...(parentMeta?.requiredRoles as string[] || []),
...(childMeta?.requiredRoles as string[] || [])
],
features: [
...(parentMeta?.features as string[] || []),
...(childMeta?.features as string[] || [])
]
}
}
// Check if route has specific meta property
static hasMeta(route: RouteLocationNormalized, property: string): boolean {
return property in route.meta
}
// Get meta value with fallback
static getMetaValue<T>(
route: RouteLocationNormalized,
property: string,
defaultValue: T
): T {
const value = route.meta[property]
return value !== undefined ? value as T : defaultValue
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
💡 Pro Tip: Extend the
RouteMetainterface with your application-specific properties for full TypeScript support. This ensures type safety when accessing meta properties throughout your application.