Learn Vue - Caching & Performance
Series/Learn Vue/Episode 14
Episode 14 of 24

Learn Vue - Caching & Performance

This episode covers performance and caching in Vue: client-side caching with Vue Query and local storage, memoization with computed and watchEffect, lazy loading components, plus techniques for optimizing rendering and reactivity.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

A slow application drives users away before the data even loads. Caching and performance aren't optional features — they're part of the user experience. The fewer wasted requests and the fewer unnecessary renders, the faster the application feels.

Episode 14 covers caching and optimization strategies in Vue: client-side caching with Vue Query and local storage, memoization with computed and watchEffect, lazy loading components for code splitting, and techniques for optimizing rendering and reactivity.

Client-side Caching with Vue Query

Install and Setup

TanStack Query provides server data caching on the client:

Install Vue Query
npm install @tanstack/vue-query
JSPasang plugin Vue Query
import { createApp } from "vue";
import { VueQueryPlugin } from "@tanstack/vue-query";
import App from "./App.vue";
 
createApp(App).use(VueQueryPlugin).mount("#app");

VueQueryPlugin is registered once at the root. After that, every query uses the internal cache with keys, staleTime, and automatic invalidation.

useQuery with staleTime

Queries that are called often can reuse cached data for longer:

JSQuery dengan cache
<script setup>
import { useQuery } from "@tanstack/vue-query";
 
const { data, isPending, error } = useQuery({
  queryKey: ["produk"],
  queryFn: async () => {
    const res = await fetch("/api/produk");
    return res.json();
  },
  staleTime: 5 * 60 * 1000,
});
</script>
 
<template>
  <p v-if="isPending">Memuat...</p>
  <p v-if="error">Gagal memuat</p>
  <ul>
    <li v-for="item in data" :key="item.id">{{ item.nama }}</li>
  </ul>
</template>

staleTime: 5 * 60 * 1000 keeps the data considered fresh for five minutes, so navigating back and forth doesn't resend the request.

Caching with Local Storage

The useLocalCache Composable

For data that rarely changes, store fetch results in local storage with an expiration time:

JSComposable useLocalCache
import { ref } from "vue";
 
export function useLocalCache(key) {
  const data = ref(JSON.parse(localStorage.getItem(key)));
 
  function simpan(nilai) {
    localStorage.setItem(key, JSON.stringify(nilai));
    data.value = nilai;
  }
 
  function hapus() {
    localStorage.removeItem(key);
    data.value = null;
  }
 
  return { data, simpan, hapus };
}

useLocalCache("produk-terbaru") reads the cache up front and writes through simpan. Typically the cache is read before a request is sent and written after the fetch completes.

Memoization with computed and watchEffect

computed for Expensive Values

computed caches its result and only recalculates when a dependency changes:

JSComputed di-cache
import { computed } from "vue";
 
const daftar = ref([]);
const totalBerat = computed(() =>
  daftar.value.reduce((total, item) => total + item.berat, 0)
);

totalBerat is only recalculated when daftar changes, not on every render. This stops expensive calculations from running over and over.

watchEffect with Cleanup

watchEffect is great for side effects like intervals or analytics sending, and its cleanup prevents leaks:

JSwatchEffect dengan cleanup
import { watchEffect } from "vue";
 
watchEffect((onCleanup) => {
  const timer = setInterval(() => {
    console.log("detak");
  }, 1000);
 
  onCleanup(() => clearInterval(timer));
});

onCleanup(() => clearInterval(timer)) is called before the effect runs again or when the component unmounts, so no interval is left idle.

Lazy Loading and Code Splitting

Async Components

Large components are loaded only when needed:

JSAsync component
<script setup>
import { defineAsyncComponent } from "vue";
 
const ChartHarian = defineAsyncComponent(
  () => import("../components/ChartHarian.vue")
);
</script>
 
<template>
  <ChartHarian />
</template>

defineAsyncComponent(() => import(...)) splits a component's code into a separate chunk that downloads when the component is rendered. Combine it with the dynamic routes from episode 9 for thorough code splitting.

Optimizing Rendering and Reactivity

  • Use shallowRef for large data replaced wholesale instead of mutated field by field.
  • Limit reactivity: don't make reactive for values that are only read once.
  • Always give v-for a stable key.
  • Use v-memo for lists that only partially change.

Tip

Measure before optimizing. Use Vue Devtools and the Performance panel to find the real bottlenecks, then apply the techniques from this episode.

Summary

Episode 14 gave you a performance toolkit: Vue Query for server data cache with staleTime, local storage for rarely changing data, computed and watchEffect for memoization, defineAsyncComponent for code splitting, plus economical reactivity rules.

Key takeaways:

  • Vue Query stores server data cache on the client.
  • staleTime determines how long data is considered fresh.
  • Local storage fits data that changes rarely.
  • computed caches expensive derived values.
  • defineAsyncComponent splits component code.
  • Measure performance before optimizing.

In the next episode 15, we'll cover performance optimization — profiling with Vue Devtools and browser tools, optimizing component re-renders, virtual scrolling for large lists, and asset prefetching with resource hints.

Learn Vue - Caching & Performance | Learn Vue