I’m building an application using Nuxt 3, I want to add a page loader until the website loads.
-
1Does this answer your question? https://stackoverflow.com/a/53195180/15351296 – Amini Oct 22 '22 at 06:53
5 Answers
According to this article. There are a simple but limited solution and a fully customized one.
Built in <NuxtLoadingIndicator>
<NuxtLoadingIndicator>
includes a loading progress bar and can be used like this
<template>
<NuxtLayout>
<NuxtLoadingIndicator />
<NuxtPage />
<NuxtLayout>
</template>
But it has only one pre-defined UI and it can only customized with these few properties
- Color: The color of the loading bar.
- Height: Height of the loading bar, in pixels (default is 3).
- Duration: Duration of the loading bar, in milliseconds (default is 2000).
- Throttle: Throttle the appearing and hiding, in milliseconds (default is 200).
Customizable Loader
If you need your custom loader like full-screen spinner with the backdrop, you need the different approach. This approach allows you to create any loader and show it when needed.
<template>
<div class="h-screen">
<div v-if="loading" class="fixed left-0 top-0 h-0.5 w-full z-50 bg-green-500" />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
<script setup lang="ts">
const nuxtApp = useNuxtApp();
const loading = ref(false);
nuxtApp.hook("page:start", () => {
loading.value = true;
});
nuxtApp.hook("page:finish", () => {
loading.value = false;
});
</script>
Nuxt3 provides application runtime hooks that have interceptors for page:start
and page:finish
events. To use them properly you need to use these hooks in this way (in the app.vue or your custom component):

- 621
- 1
- 5
- 21
-
I think setting `true` as initial value to `loading` variable is more logical solution – selim Jul 25 '23 at 05:06
-
1@selim yeah he probably going to set it to true initially. It's just an example he's use case may require adding changes – Muhammad Mahmoud Jul 27 '23 at 14:45
"The <NuxtLoadingIndicator>
component displays a progress bar on page navigation." – from the Nuxt3 Documentation for <NuxtLoadingIndicator>
.
I think that is what's used to display the loading indicator in dev setup as well.

- 472
- 1
- 4
- 19
-
2Looking through the source code for `NuxtLoadingIndicator` it doesn't actually show the current loading progress of the page and is simply just a bar that will grow over a given time. It's a quick and easy solution but doesn't give any meaningful information to the user. – The Sloth Jan 04 '23 at 16:14
i had made a custom solution for Nuxt3, using pinia for state management and global middleware and this solution works same as Nextjs-ProgressBar
store/ui.js
import { defineStore } from "pinia";
const useUiStore = defineStore("uiStore", {
state: () => {
return {
pageLoader: false,
};
},
actions: {
setPageLoader(value) {
this.pageLoader = value
},
},
});
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUiStore, import.meta.hot));
}
export default useUiStore;
middleware/ui.global.js
import useUiStore from "~~/stores/ui";
export default defineNuxtRouteMiddleware(async(to, from) => {
useUiStore().setPageLoader(true)
})
component/LoaderIndicator.vue
<script setup>
import useUiStore from '~~/stores/ui';
const nuxtApp = useNuxtApp();
nuxtApp.hook("page:finish", () => {
if(useUiStore().$state.pageLoader == true){
useUiStore().setPageLoader(false)
}
});
</script>
<script>
export default {
watch: {
showLoader(newVal, oldVal) {
if (newVal == true) {
this.$refs.progressBar.style.opacity = '1';
this.$refs.progressBar.style.transitionDuration = '1000ms';
this.$refs.progressBar.style.width = '40%';
} else if (newVal == false) {
this.$refs.progressBar.style.transitionDuration = '1000ms';
this.$refs.progressBar.style.width = '100%';
setTimeout(() => {
this.$refs.progressBar.style.opacity = '0';
this.$refs.progressBar.style.transitionDuration = '0ms';
this.$refs.progressBar.style.width = '0%';
}, 1000);
}
},
},
computed: {
showLoader() {
return useUiStore().$state.pageLoader;
}
},
};
</script>
<template>
<div ref="progressBar" class="fixed left-0 top-0 z-50 transition-all bg-orange h-[3px] LoadingBar shadow-[0_0_10px] shadow-orange " :style="{ width :'0%' }"></div>
</template>
app.vue
<template>
<NuxtLayout>
<LoaderIndicator />
<NuxtPage />
</NuxtLayout>
</template>
please comment for optimisation
Thank You

- 409
- 1
- 4
- 13
Instead of using <NuxtLoadingIndicator>
I've used global middleware + page:finish
hook like:
<template>
<div v-show="show">
Loading...
</div>
</template>
<script setup>
const nuxtApp = useNuxtApp();
const show = ref(false);
addRouteMiddleware('global-loader', () => {
show.value = true
}, {
global: true
})
nuxtApp.hook('page:finish', () => { show.value = false; })
</script>
So on every route change, we show the loader and hide it when the page is displayed.
You can replace template with the one you want, some spinner or something.

- 4,195
- 2
- 27
- 31
I think this also helpful but it involves javascript.
<div id="loading">Loading...</div>
~/plugins/loading.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('page:finish', () => {
const loadingElement = document.querySelector('#loading')
if (loadingElement) {
loadingElement.remove()
}
})
})
Once the hook triggers, it will remove the spinner in the document.

- 1,290
- 1
- 11