RouterView Slot API | Vue Router - Advanced Component Rendering ๐จ โ
Complete rendering control - Customize how route components are rendered with full access to routing context!
๐ฏ Overview โ
The RouterView slot API provides complete control over how route components are rendered. Instead of the default behavior, you can use the v-slot API to access the current component, route information, and implement custom rendering logic.
๐ Key Benefits โ
- Custom Transitions: Implement complex transition patterns
- Loading States: Show loading indicators while components load
- Error Boundaries: Handle component loading errors gracefully
- Conditional Rendering: Render different content based on route state
- Advanced Layouts: Create complex layout systems
๐ก When to Use the Slot API โ
๐จ Advanced UI Requirements โ
Complex Transitions
- Page-specific animations
- Shared element transitions
- Staggered loading effects
Enhanced User Experience
- Custom loading states
- Error recovery mechanisms
- Progressive enhancement
๐ง Technical Scenarios โ
vue
<RouterView v-slot="{ Component, route }">
<!-- Custom transition with loading state -->
<Transition :name="route.meta.transition || 'fade'">
<Suspense>
<component :is="Component" :key="route.fullPath" />
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</Transition>
</RouterView>๐ Basic Slot Usage โ
Accessing Slot Properties โ
The slot provides access to two main properties:
vue
<template>
<RouterView v-slot="{ Component, route }">
<!-- Component: The Vue component to render -->
<!-- route: Current route information -->
<component :is="Component" :key="route.fullPath" />
</RouterView>
</template>Simple Custom Rendering โ
vue
<template>
<RouterView v-slot="{ Component, route }">
<div class="page-container">
<!-- Custom header for each route -->
<header class="page-header">
<h1>{{ route.meta.title || 'My App' }}</h1>
<nav class="breadcrumb">
<Breadcrumb :route="route" />
</nav>
</header>
<!-- Main content area -->
<main class="page-content">
<component :is="Component" :key="route.fullPath" />
</main>
</div>
</RouterView>
</template>
<script setup>
import Breadcrumb from './components/Breadcrumb.vue'
</script>
<style scoped>
.page-container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.page-header {
background: #f8f9fa;
padding: 1rem;
border-bottom: 1px solid #dee2e6;
}
.page-content {
flex: 1;
padding: 2rem;
}
</style>๐ง Advanced Techniques โ
Example 1: Advanced Transition System โ
vue
<template>
<RouterView v-slot="{ Component, route }">
<!-- Dynamic transitions based on route meta -->
<Transition
:name="getTransitionName(route)"
:mode="route.meta.transitionMode || 'out-in'"
@enter="onPageEnter"
@leave="onPageLeave"
>
<!-- Keep components alive based on route meta -->
<KeepAlive :include="getCachedComponents(route)">
<component
:is="Component"
:key="route.fullPath"
class="route-component"
/>
</KeepAlive>
</Transition>
</RouterView>
</template>
<script setup>
import { ref, computed } from 'vue'
// Track navigation history for transition direction
const navigationHistory = ref([])
function getTransitionName(route) {
const fromIndex = navigationHistory.value.indexOf(route.from?.path)
const toIndex = navigationHistory.value.indexOf(route.path)
if (fromIndex < toIndex) {
return 'slide-left' // Moving forward
} else if (fromIndex > toIndex) {
return 'slide-right' // Moving backward
}
return route.meta.transition || 'fade' // Default transition
}
function getCachedComponents(route) {
return route.meta.keepAlive ? [route.name].filter(Boolean) : []
}
function onPageEnter(el) {
console.log('Page entered:', el)
// Add any enter animations or logic
}
function onPageLeave(el) {
console.log('Page leaving:', el)
// Add any leave animations or logic
}
// Update navigation history
import { useRouter } from 'vue-router'
const router = useRouter()
router.afterEach((to, from) => {
// Add new route to history
if (to.path && !navigationHistory.value.includes(to.path)) {
navigationHistory.value.push(to.path)
}
})
</script>
<style>
/* Slide transitions */
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: all 0.3s ease;
}
.slide-left-enter-from {
opacity: 0;
transform: translateX(30px);
}
.slide-left-leave-to {
opacity: 0;
transform: translateX(-30px);
}
.slide-right-enter-from {
opacity: 0;
transform: translateX(-30px);
}
.slide-right-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* Fade transition */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>Example 2: Error Boundary with Recovery โ
vue
<template>
<RouterView v-slot="{ Component, route }">
<div class="error-boundary">
<component
v-if="!hasError"
:is="Component"
:key="route.fullPath"
@error="handleComponentError"
/>
<div v-else class="error-fallback">
<h2>Something went wrong</h2>
<p>Unable to load the page: {{ errorMessage }}</p>
<button @click="handleRetry" class="retry-button">
Try Again
</button>
<button @click="goBack" class="back-button">
Go Back
</button>
</div>
</div>
</RouterView>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const hasError = ref(false)
const errorMessage = ref('')
function handleComponentError(error) {
console.error('Component error:', error)
hasError.value = true
errorMessage.value = error.message || 'Unknown error occurred'
}
function handleRetry() {
hasError.value = false
errorMessage.value = ''
// Force re-render of the component
}
function goBack() {
router.back()
}
</script>
<style scoped>
.error-boundary {
min-height: 400px;
}
.error-fallback {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
text-align: center;
background: #f8f9fa;
border-radius: 8px;
margin: 2rem;
}
.retry-button {
background: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin: 0.5rem;
}
.back-button {
background: #6c757d;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin: 0.5rem;
}
.retry-button:hover {
background: #0056b3;
}
.back-button:hover {
background: #545b62;
}
</style>๐ ๏ธ Practical Examples โ
Example 3: Layout System with Slot API โ
vue
<template>
<RouterView v-slot="{ Component, route }">
<!-- Dynamic layout based on route meta -->
<component :is="getLayoutComponent(route)">
<!-- Page content -->
<component :is="Component" :key="route.fullPath" />
</component>
</RouterView>
</template>
<script setup>
import { computed } from 'vue'
// Layout components
const Layouts = {
default: () => import('./layouts/DefaultLayout.vue'),
admin: () => import('./layouts/AdminLayout.vue'),
auth: () => import('./layouts/AuthLayout.vue'),
minimal: () => import('./layouts/MinimalLayout.vue')
}
function getLayoutComponent(route) {
const layoutName = route.meta.layout || 'default'
return Layouts[layoutName] || Layouts.default
}
</script>Example 4: Progressive Enhancement โ
vue
<template>
<RouterView v-slot="{ Component, route }">
<div class="progressive-enhancement">
<!-- Basic content for slow connections -->
<div v-if="!isEnhanced" class="basic-content">
<h1>{{ route.meta.title }}</h1>
<LoadingIndicator />
<button @click="enableEnhancedMode" class="enhance-button">
Enable Enhanced Mode
</button>
</div>
<!-- Enhanced content for fast connections -->
<Suspense v-else>
<component :is="Component" :key="route.fullPath" />
<template #fallback>
<EnhancedLoading />
</template>
</Suspense>
</div>
</RouterView>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const isEnhanced = ref(false)
function enableEnhancedMode() {
isEnhanced.value = true
}
// Auto-enable if connection is fast
onMounted(() => {
if (navigator.connection?.effectiveType === '4g') {
isEnhanced.value = true
}
})
</script>๐ Best Practices โ
โ Do โ
- Use for Complex Scenarios: When default behavior isn't sufficient
- Implement Error Boundaries: Handle component loading failures
- Optimize Performance: Use keys and KeepAlive appropriately
- Provide Loading States: Keep users informed during transitions
- Test Thoroughly: Ensure custom rendering works correctly
โ Don't โ
- Overcomplicate Simple Cases: Use default behavior when possible
- Ignore Accessibility: Ensure custom rendering is accessible
- Forget Mobile: Test on different devices and connection speeds
- Skip Error Handling: Always prepare for potential failures
๐ Related Features โ
- Transition Component: For smooth page transitions
- KeepAlive Component: For component state preservation
- Suspense Component: For async component loading
- Error Boundaries: For component error handling
๐ก Pro Tip
Combine the RouterView slot API with Vue's Composition API to create reusable rendering patterns that can be shared across different parts of your application!
Ready for advanced rendering control? Start using the RouterView slot API to create sophisticated, user-friendly navigation experiences! ๐จ