When does each hook run?
Vue and Nuxt’s reactivity makes apps feel intuitive, but lifecycles can be tricky.
When navigating between pages, which hook runs when? When does the layout mount? Where does useAsyncData kick in?
The Core Idea
Nuxt 3 has three main layers:- Layout → The skeleton (e.g., navbar, footer).
- Page → Route-specific content.
- Component → Child components used by the page.
Initial Page Load (SSR Phase)
On the very first load with SSR, events occur in this order:- Server-side setup →
useAsyncData,useFetchkick off. - Server render → HTML is produced and sent.
- Hydration → The client boots the Vue app.
- onBeforeMount → Right before nodes attach to the DOM.
- onMounted → The DOM is ready—do browser-only work.
setup() and useAsyncData() run. Hooks like onMounted run only on the client.Route Change Flow (CSR Phase)
When navigating client-side, SSR is out of the picture:- onBeforeRouteLeave (old page) → Runs before leaving. Use for cleanup or guards.
- onBeforeRouteUpdate (layout/page) → Fires when route params change under the same layout.
- Page
setup()→ The new page’s setup runs; calluseAsyncDatahere. - onBeforeMount (new page) → Before mounting to the DOM.
- onMounted (new page) → After visual render.
- onUpdated (page/component) → After reactive updates change the DOM.
Layout vs Page Hook Hierarchy
| Phase | Layout Hook | Page Hook |
|---|---|---|
| Server Render | setup() | setup() |
| Hydration | onBeforeMount | onBeforeMount |
| Mount | onMounted | onMounted |
| Route Update | onBeforeRouteUpdate | onBeforeRouteUpdate |
| Unmount | onUnmounted | onUnmounted |
Example Flow: Blog List → Blog Detail
Going from/blog to /blog/123:- The layout (
default.vue) stays; only the page updates. onBeforeRouteUpdatetriggers at both layout and page levels.- Page
setupruns again (useAsyncDatarefetches). onMountedfires when new content is in the DOM.
Using onBeforeRouteLeave and onBeforeRouteUpdate
Warn before leaving a page:onBeforeRouteLeave((to, from) => {
if (formChanged.value) {
const confirmLeave = confirm('Unsaved changes. Leave anyway?')
if (!confirmLeave) return false
}
})
Refetch on param change:onBeforeRouteUpdate((to) => {
fetchPost(to.params.id)
})
This updates data on param-only changes without recreating the page.Pro Tip: Global Hook Management
You can add global hooks inapp.vue or layouts/default.vue. For example, a loading animation during route changes:onBeforeRouteUpdate(() => startLoading())
onBeforeRouteLeave(() => stopLoading())
Or define global router guards in a plugin:export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('page:start', () => console.log('Loading new page...'))
nuxtApp.hook('page:finish', () => console.log('Page loaded!'))
})
Lifecycle Order — Quick Summary
SSR phase: setup() → render → hydration
CSR phase: onBeforeRouteLeave → setup() → onMounted → onUpdated → onUnmounted
The layout remounts only when it changes. The page component runs on every route change.Common Pitfalls
- Calling useAsyncData inside
onMounted→ misses SSR data. - Fetching with
useRouteparams only inonMounted→ won’t update on param changes. - Assuming the layout is implicitly
keep-alive→ it can remount; state may reset. - Forgetting to reset reactive state on route change → stale data leaks into new pages.
Conclusion
Nuxt 3 balances SSR and CSR lifecycles elegantly. Understanding the order helps you place data fetching, animations, cache, and auth at the right moments.In short:
- SSR →
setup&useAsyncData - CSR →
onBeforeRouteUpdate&onMounted - Layout → remounts only when it changes
Use the right hook at the right time for a smoother Nuxt app flow.