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.

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.
TanStack Query provides server data caching on the client:
npm install @tanstack/vue-queryimport { 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.
Queries that are called often can reuse cached data for longer:
<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.
For data that rarely changes, store fetch results in local storage with an expiration time:
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.
computed caches its result and only recalculates when a dependency changes:
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 is great for side effects like intervals or analytics sending, and its cleanup prevents leaks:
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.
Large components are loaded only when needed:
<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.
shallowRef for large data replaced wholesale instead of mutated field by field.reactive for values that are only read once.v-for a stable key.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.
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:
staleTime determines how long data is considered fresh.computed caches expensive derived values.defineAsyncComponent splits component code.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.