Bundle Optimization

Preview — 3 of 10 questions

What are the two main strategies for virtual scrolling and what are their tradeoffs?

javascript
<script setup>
import { useVirtualizer } from '@tanstack/vue-virtual'
import { ref } from 'vue'

const parentRef = ref(null)
const items = ref(generateItems(100000))

// Strategy 1: Fixed-size (all items same height — simpler, faster)
const fixedVirtualizer = useVirtualizer({
  count: items.value.length,
  getScrollElement: () => parentRef.value,
  estimateSize: () => 56, // All rows are exactly 56px
  overscan: 5,            // Render 5 extra items above/below viewport
})

// Strategy 2: Variable-size (items have different heights — measured at runtime)
const variableVirtualizer = useVirtualizer({
  count: items.value.length,
  getScrollElement: () => parentRef.value,
  estimateSize: (i) => items.value[i].type === 'header' ? 80 : 48,
  // measureElement: (el) => el.getBoundingClientRect().height — for dynamic
})
</script>

<template>
  <div ref="parentRef" class="scroll-container" style="height: 500px; overflow-y: auto;">
    <!-- Total height spacer  makes the scrollbar proportional to total items -->
    <div :style="{ height: `${fixedVirtualizer.getTotalSize()}px`, position: 'relative' }">
      <div
        v-for="row in fixedVirtualizer.getVirtualItems()"
        :key="row.key"
        :style="{
          position: 'absolute',
          top: `${row.start}px`,
          left: 0,
          width: '100%',
          height: `${row.size}px`
        }"
      >
        <ItemRow :item="items[row.index]" />
      </div>
    </div>
  </div>
</template>
ADOM recycling vs. element caching — DOM recycling is always faster
BCSS-based virtualization vs. JavaScript-based virtualization — CSS is always preferred
CFixed-size virtualization (known item height) vs. variable-size virtualization (measured at runtime) — fixed is simpler and faster; variable requires measurement
DColumn virtualization vs. row virtualization — use column for wide tables, row for long lists

How do you preload async components before the user navigates to them?

javascript
// Strategy 1: Vite's build-level prefetch
// vite.config.ts
export default defineConfig({
  build: {
    modulePreload: {
      polyfill: true,
      resolveDependencies: (filename, deps, context) => {
        return deps // Preload all dependencies
      }
    }
  }
})

// Strategy 2: Hover-based prefetch in router-link
// On user hover, preload the chunk before they click
const AdminView = defineAsyncComponent(() => import('./views/AdminView.vue'))

function prefetchAdminRoute() {
  import('./views/AdminView.vue') // Trigger download
}

// Strategy 3: IntersectionObserver-based prefetch
// When a nav item scrolls into view, prefetch its route
const useRoutePrefetch = (routeLoader) => {
  const linkRef = ref(null)

  useIntersectionObserver(linkRef, ([{ isIntersecting }]) => {
    if (isIntersecting) {
      routeLoader() // Trigger dynamic import
    }
  })

  return linkRef
}

// Strategy 4: requestIdleCallback — prefetch when browser is idle
if ('requestIdleCallback' in window) {
  requestIdleCallback(() => {
    import('./views/LikelyNextRoute.vue')
  }, { timeout: 2000 })
}
AUse <link rel="prefetch"> for route-level chunks or call import() on user interaction signals (hover, focus)
BSet preload: true in defineAsyncComponent options
CVue automatically preloads all async components on initial page load
DUse router.prefetch() — a built-in Vue Router method for preloading route components

What is the optimal chunk splitting strategy for a large Vue application?

javascript
// vite.config.ts — production chunk strategy
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // Chunk 1: Vue core (rarely changes — long cache lifetime)
          if (id.includes('node_modules/vue') ||
              id.includes('node_modules/@vue')) {
            return 'vue-core'
          }

          // Chunk 2: Large vendor libraries (Monaco editor, Chart.js)
          if (id.includes('node_modules/monaco-editor')) {
            return 'monaco'
          }
          if (id.includes('node_modules/chart.js')) {
            return 'charts'
          }

          // Chunk 3: Utility vendors (lodash, date-fns)
          if (id.includes('node_modules/lodash') ||
              id.includes('node_modules/date-fns')) {
            return 'utils'
          }

          // Chunk 4: Admin routes (rarely visited by most users)
          if (id.includes('/views/admin/') ||
              id.includes('/components/admin/')) {
            return 'admin'
          }

          // Everything else stays in the main bundle
        }
      }
    }
  }
})
ANever split — one large bundle is faster because it requires only one HTTP request
BOnly split when the bundle exceeds 5MB — smaller bundles don't need splitting
CSplit every component into its own chunk for maximum granularity
DSplit vendor libs from app code; split rarely-visited routes into separate chunks; keep frequently visited routes in the main bundle

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.