Skip to content

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
  • 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! ๐ŸŽจ

๐Ÿš€ Vue Router - ่ฎฉๅ‰็ซฏ่ทฏ็”ฑๅ˜ๅพ—็ฎ€ๅ•่€Œๅผบๅคง | ๆž„ๅปบ็ŽฐไปฃๅŒ–ๅ•้กตๅบ”็”จ็š„ๆœ€ไฝณ้€‰ๆ‹ฉ