Chapter 108: Vue ‘serverPrefetch’ Lifecycle Hook
ServerPrefetch
(or its modern Composition API name onServerPrefetch)
This hook is not part of the normal client-side lifecycle (mounted, updated, unmounted etc.). It exists exclusively for server-side rendering scenarios — most commonly when you use:
- Vue SSR directly (@vue/server-renderer)
- Nuxt 3
- Vite SSR
- Quasar SSR mode
- Any other setup that renders Vue components on the server before sending HTML to the browser
1. Mental model – What problem does serverPrefetch solve?
In a classic client-side Vue app:
- Browser downloads JS bundle
- Vue boots up
- onMounted → you fetch data → show loading → then show content
→ User sees white screen / loading spinner for 500–2000 ms (or more on slow 3G)
In SSR:
- Server renders the initial HTML with data already filled in
- Browser receives fully rendered HTML → content appears instantly (great for SEO, first paint speed, Core Web Vitals)
- Then client “hydrates” the HTML → attaches reactivity
Problem: How can you fetch data on the server so that the initial HTML already contains the real content (not just loading placeholders)?
Answer: serverPrefetch / onServerPrefetch
This hook runs only on the server during SSR rendering. It lets you perform async operations (usually data fetching) before the component is serialized to HTML.
2. Key facts about onServerPrefetch
| Fact | Explanation |
|---|---|
| Runs only on server | Never called in client-side rendering (browser) |
| Runs during SSR render | Before component HTML is generated and sent to browser |
| Can be async | You can await inside it — Vue waits for all promises to resolve |
| Purpose | Fetch data, fill state → so initial HTML already has real content |
| Modern name | onServerPrefetch(() => { … }) |
| Options API name | serverPrefetch() { … } (still supported, but rarely used now) |
| Works with <Suspense> | Yes — very common pattern in Nuxt 3 / Vue SSR |
| Runs before onBeforeMount | Server render order: setup → serverPrefetch → render → HTML |
3. Real, Practical Example – User Profile with Server-Fetched Data
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<!-- UserProfile.vue --> <template> <div class="user-profile"> <h2 v-if="user">{{ user.name }}</h2> <p v-else>Loading user…</p> <div v-if="user"> <p>Email: {{ user.email }}</p> <p>Joined: {{ user.createdAt | formatDate }}</p> </div> </div> </template> <script setup lang="ts"> import { ref, onServerPrefetch } from 'vue' // Reactive state – will be filled either on server or client const user = ref(null) // This runs ON SERVER during SSR (and on client during hydration if needed) onServerPrefetch(async () => { console.log('Running on server (or during hydration)') try { // Fake API call – in real app use axios/fetch with server context const response = await fetch('https://api.example.com/users/123', { headers: { 'User-Agent': 'SSR' } // optional – helps server distinguish }) if (!response.ok) throw new Error('Failed to fetch user') user.value = await response.json() console.log('User loaded on server:', user.value.name) } catch (err) { console.error('Server prefetch failed:', err) // Optional: set fallback / error state user.value = { name: 'Guest', email: 'error@example.com' } } }) // Normal client-only logic (runs after hydration) onMounted(() => { if (!user.value) { console.log('Client-side fallback fetch') // Optional: fetch again only if server failed } }) </script> <style scoped> .user-profile { padding: 2rem; background: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); max-width: 500px; margin: 2rem auto; } </style> |
What happens in SSR (server) mode:
- Request comes to server
- Vue starts rendering <UserProfile />
- onServerPrefetch runs → awaits API call
- user.value is filled with real data
- Template renders with real name/email → HTML sent to browser looks like:
HTML012345678910<div class="user-profile"><h2>Rahul Sharma</h2><p>Email: rahul@example.com</p>…</div>
- Browser receives ready HTML → instant content (great SEO, fast FCP/LCP)
- Client hydrates → attaches reactivity → onMounted runs (but sees data already there → no extra fetch)
What happens without onServerPrefetch?
- Server renders <p>Loading user…</p>
- Browser shows loading → then client fetches → flicker / delay
4. Very Important Patterns & Gotchas (2026 Must-Know)
| Pattern / Gotcha | Best Practice / Reality in 2026 |
|---|---|
| Works with <Suspense> | Yes – very common: <Suspense><UserProfile /></Suspense> → shows fallback until prefetch done |
| Multiple onServerPrefetch | Vue waits for all of them to resolve before rendering |
| Error handling | Wrap in try/catch → set fallback state → do not let error escape (crashes SSR) |
| Runs on client too? | Yes during hydration if data was not prefetched (rare with good SSR) |
| Nuxt 3 / Nitro | useAsyncData / useFetch already handles this → onServerPrefetch less needed |
| When NOT to use | Pure client-side apps, static sites without SSR, components that don’t need server data |
| Still used in 2026? | Yes — heavily in Vue SSR, custom SSR setups, some Nuxt 3 patterns |
Quick Summary Table – serverPrefetch / onServerPrefetch in 2026
| Question | Answer / Reality in 2026 |
|---|---|
| When does it run? | Only during SSR render (server) + during client hydration if needed |
| Is it async? | Yes – Vue waits for all promises to resolve |
| DOM available? | No – runs before any DOM creation |
| Do modern developers use it? | Yes – in SSR / Nuxt / custom SSR setups |
| Modern name | onServerPrefetch(async () => { … }) |
| Nuxt 3 equivalent | useAsyncData / useFetch (usually preferred) |
| Still asked in interviews? | Occasionally – mostly in SSR/Nuxt-related positions |
Pro Tips from Real Projects (Hyderabad 2026)
- In Nuxt 3 → prefer useAsyncData / useFetch → they automatically handle serverPrefetch under the hood + give nice loading/error states
- In plain Vue SSR → onServerPrefetch is still the cleanest way to fetch per-component data
- Always catch errors inside onServerPrefetch → otherwise SSR crashes → 500 error for user
- Combine with <Suspense> → shows fallback UI while prefetch is running
- Test SSR → use npm run ssr or Nuxt dev → check HTML source → real data should be there
- Avoid heavy computations in onServerPrefetch — server render time affects TTFB
Your mini homework:
- Create the UserProfile component above with onServerPrefetch
- Run a simple SSR server (or imagine Nuxt) → check HTML source → see real user name already in HTML
- Remove onServerPrefetch → see “Loading user…” in source → then client fetch flicker
- Add <Suspense> wrapper → see fallback while prefetch runs
Any part confusing? Want full examples for:
- onServerPrefetch + <Suspense> + loading/error states?
- Nuxt 3 useAsyncData vs plain onServerPrefetch comparison?
- serverPrefetch + useFetch + useAsyncData patterns side-by-side?
- Real SSR component (user profile, product page, blog post) with prefetch?
Just tell me — we’ll build the next SEO-friendly, fast-first-paint SSR component together step by step 🚀
