Add NProgress Loader
March 14, 2025 Β· AI Conversations
### **Steps to Implement the Loader Bar:**
#### **1. Install NProgress**
Run the following command inside your project directory:
yarn add nprogress
or
npm install nprogress
#### **2. Configure the Progress Bar in `app.js` (or wherever you initialize Vue & Inertia)**
Modify your `resources/js/app.js` file to integrate **NProgress** with Inertia.
import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
import { InertiaProgress } from '@inertiajs/progress';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; // Import the styles
// Configure NProgress
NProgress.configure({ showSpinner: false });
document.addEventListener('inertia:start', () => NProgress.start());
document.addEventListener('inertia:finish', (event) => {
if (event.detail.visit.completed) {
NProgress.done();
} else if (event.detail.visit.interrupted || event.detail.visit.cancelled) {
NProgress.done(true);
}
});
// Initialize Inertia Progress Bar
InertiaProgress.init({
color: '#29d', // Change the color of the progress bar
showSpinner: false
});
// Initialize Vue with Inertia
createInertiaApp({
resolve: (name) => import(`./Pages/${name}.vue`),
setup({ el, app, props, plugin }) {
createApp({ render: () => h(app, props) })
.use(plugin)
.mount(el);
}
});
#### **3. Customize the Progress Bar (Optional)**
If you want to customize the progress bar styles, modify **NProgress CSS** in your project:
Add this to your `resources/css/app.css` or `resources/css/custom.css`:
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: #29d; /* Change this to match your site's color */
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
opacity: 1;
transform: rotate(3deg) translate(0px, -4px);
}
#### **4. Test the Implementation**
– Restart your development server:
yarn dev
– Navigate between pages, and you should see a **blue progress bar at the top** when transitioning.
Let me know if you need any adjustments! π
<script>
import { ref,computed } from 'vue'
import { useDisplay } from 'vuetify'
import { useAuthStore } from '@/stores/auth';
import { useRouter } from 'vue-router';
import api from '@/api';
const authStore = useAuthStore();
const router = useRouter();
const isAuthenticated = computed(() => authStore.isAuthenticated);
const drawer = ref(false)
const display = useDisplay()
const logout = async () => {
try {
await api.post('/logout');
authStore.logout();
router.push('/login');
} catch (error) {
console.error('Logout failed', error);
}
};
</script>
SYT
News
mdi-video
mdi-account
mdi-login
mdi-logout
Β© {{ new Date().getFullYear() }} SYT
place footer after all body content not sticky in desktop
### **Solution:**
– **Remove the `app` prop** from “, which forces it to be part of the application layout.
– **Wrap “ and “ inside a flexbox container** so that the footer naturally follows after all the content.
– **Use Vuetify's grid system** to structure the layout correctly.
### **Updated Code:**
<script setup>
import { ref, computed } from 'vue';
import { useAuthStore } from '@/stores/auth';
import { useRouter } from 'vue-router';
import api from '@/api';
const authStore = useAuthStore();
const router = useRouter();
const isAuthenticated = computed(() => authStore.isAuthenticated);
const drawer = ref(false);
const logout = async () => {
try {
await api.post('/logout');
authStore.logout();
router.push('/login');
} catch (error) {
console.error('Logout failed', error);
}
};
</script>
<template>
<v-app>
<!-- App Bar (Header) -->
<v-app-bar color="primary" dark>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
<RouterLink to="/" style="text-decoration: none; color: inherit;">
<v-toolbar-title>SYT</v-toolbar-title>
</RouterLink>
<v-spacer></v-spacer>
<v-btn :to="`/news`"> News </v-btn>
<v-btn icon :to="`/youtube`">
<v-icon>mdi-video</v-icon>
</v-btn>
<v-btn icon :to="isAuthenticated ? `/dashboard` : `/register`">
<v-icon>mdi-account</v-icon>
</v-btn>
<v-btn icon :to="`/login`" v-if="!isAuthenticated">
<v-icon>mdi-login</v-icon>
</v-btn>
<v-btn icon @click="logout" v-if="isAuthenticated">
<v-icon>mdi-logout</v-icon>
</v-btn>
</v-app-bar>
<!-- Sidebar Navigation (Drawer) -->
<v-navigation-drawer v-model="drawer" temporary>
<v-list>
<v-list-item title="Dashboard" prepend-icon="mdi-view-dashboard" :to="`/dashboard`"></v-list-item>
<v-list-item title="Profile" prepend-icon="mdi-account" :to="`/profile`"></v-list-item>
<v-list-item title="Settings" prepend-icon="mdi-cog" :to="`/settings`"></v-list-item>
<v-list-item title="Posts" prepend-icon="mdi-format-list-bulleted" :to="`/posts`"></v-list-item>
<v-list-item title="Logout" prepend-icon="mdi-logout" @click="logout"></v-list-item>
</v-list>
</v-navigation-drawer>
<!-- Content Wrapper -->
<div class="content-wrapper">
<v-main>
<v-container>
<slot />
</v-container>
</v-main>
<!-- Footer (Not Sticky on Desktop) -->
<v-footer color="primary darken-3">
<v-container class="text-center white--text">
Β© {{ new Date().getFullYear() }} SYT
</v-container>
</v-footer>
</div>
</v-app>
</template>
<style scoped>
/* Ensure footer is placed after content and not sticky */
.content-wrapper {
display: flex;
flex-direction: column;
min-height: 100vh;
}
v-main {
flex-grow: 1;
}
</style>
### **Key Changes:**
1. **Wrapped “ and “ in `.content-wrapper`** to control layout.
2. **Used `flex-grow: 1` on “** to push the footer down only when content is less.
3. **Removed `app` prop from “**, so it's not fixed to the bottom.
Now, the footer will appear **after all the content** and will only stick to the bottom if the content is shorter than the viewport. π
—
### **Steps to Implement the Progress Bar:**
#### **1. Install NProgress**
Run the following command to install `nprogress`:
yarn add nprogress
or
npm install nprogress
—
#### **2. Configure the Progress Bar in `router.js`**
Modify your **Vue Router** setup (`src/router.js` or wherever your router is defined) to use **NProgress**.
import { createRouter, createWebHistory } from 'vue-router';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; // Import styles
// Configure NProgress
NProgress.configure({ showSpinner: false, speed: 500, minimum: 0.2 });
// Import your views
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
];
const router = createRouter({
history: createWebHistory(),
routes
});
// Start progress bar before navigation
router.beforeEach((to, from, next) => {
NProgress.start();
next();
});
// Complete progress bar after navigation
router.afterEach(() => {
NProgress.done();
});
export default router;
—
#### **3. Customize Progress Bar (Optional)**
If you want to **customize the color and appearance**, add the following styles to your `src/assets/nprogress.css` (or in your global CSS file).
/* Progress Bar */
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: #29d; /* Change to match your site's theme */
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 3px;
}
/* Optional: Progress Peg */
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
opacity: 1;
transform: rotate(3deg) translate(0px, -4px);
}
—
#### **4. Import Router in `main.js`**
Make sure your `main.js` includes Vue Router:
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import 'nprogress/nprogress.css'; // Import NProgress styles
const app = createApp(App);
app.use(router);
app.mount('#app');
—
### **Testing**
1. Start your app:
yarn dev
or
npm run dev
2. Navigate between pages and see the **progress bar at the top** while the page is loading.
Now you have a **smooth navigation progress bar** in your Vue 3 standalone application! π
—
### **Updated Code with Mobile-Specific Sticky Footer**
<template>
<v-app>
<!-- App Bar (Header) -->
<v-app-bar color="primary" dark>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
<RouterLink to="/" style="text-decoration: none; color: inherit;">
<v-toolbar-title>SYT</v-toolbar-title>
</RouterLink>
<v-spacer></v-spacer>
<v-btn :to="`/news`"> News </v-btn>
<v-btn icon :to="`/youtube`">
<v-icon>mdi-video</v-icon>
</v-btn>
<v-btn icon :to="isAuthenticated ? `/dashboard` : `/register`">
<v-icon>mdi-account</v-icon>
</v-btn>
<v-btn icon :to="`/login`" v-if="!isAuthenticated">
<v-icon>mdi-login</v-icon>
</v-btn>
<v-btn icon @click="logout" v-if="isAuthenticated">
<v-icon>mdi-logout</v-icon>
</v-btn>
</v-app-bar>
<!-- Sidebar Navigation (Drawer) -->
<v-navigation-drawer v-model="drawer" temporary>
<v-list>
<v-list-item title="Dashboard" prepend-icon="mdi-view-dashboard" :to="`/dashboard`"></v-list-item>
<v-list-item title="Profile" prepend-icon="mdi-account" :to="`/profile`"></v-list-item>
<v-list-item title="Settings" prepend-icon="mdi-cog" :to="`/settings`"></v-list-item>
<v-list-item title="Posts" prepend-icon="mdi-format-list-bulleted" :to="`/posts`"></v-list-item>
<v-list-item title="Logout" prepend-icon="mdi-logout" @click="logout"></v-list-item>
</v-list>
</v-navigation-drawer>
<!-- Main Content -->
<div class="content-wrapper">
<v-main>
<v-container>
<slot />
</v-container>
</v-main>
<!-- Footer (Sticky on Mobile, Normal on Desktop) -->
<v-footer color="primary darken-3" :app="display.mobile">
<v-container class="text-center white--text">
Β© {{ new Date().getFullYear() }} SYT
</v-container>
</v-footer>
</div>
</v-app>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useAuthStore } from '@/stores/auth';
import { useRouter } from 'vue-router';
import { useDisplay } from 'vuetify';
import api from '@/api';
const authStore = useAuthStore();
const router = useRouter();
const isAuthenticated = computed(() => authStore.isAuthenticated);
const display = useDisplay(); // Vuetify display helper
const drawer = ref(false);
const logout = async () => {
try {
await api.post('/logout');
authStore.logout();
router.push('/login');
} catch (error) {
console.error('Logout failed', error);
}
};
</script>
<style scoped>
/* Ensure footer is placed after content and is only sticky on mobile */
.content-wrapper {
display: flex;
flex-direction: column;
min-height: 100vh;
}
v-main {
flex-grow: 1;
}
/* Mobile sticky footer */
@media (max-width: 600px) {
.v-footer {
position: fixed;
bottom: 0;
width: 100%;
z-index: 1000;
}
}
</style>
—
### **Key Changes:**
1. **Used `useDisplay()` from Vuetify** to check if the device is **mobile** (`display.mobile`).
2. **Dynamically set the `app` prop on “**:
– **On mobile (`display.mobile === true`)**, it makes the footer **sticky**.
– **On desktop (`display.mobile === false`)**, the footer is **normal (not sticky)**.
3. **Added CSS for mobile sticky behavior** using a media query (`@media (max-width: 600px)`) to **force the footer to be fixed at the bottom on mobile**.
—
### **How It Works:**
β
On **desktop**, the footer behaves normally (appears after content).
β
On **mobile**, the footer **sticks to the bottom** of the screen.
Now, your **footer is sticky only on mobile devices** and remains **non-sticky on desktop**! π
<script>
import { ref,computed } from 'vue'
import { useDisplay } from 'vuetify'
import { useAuthStore } from '@/stores/auth';
import { useRouter } from 'vue-router';
import api from '@/api';
const authStore = useAuthStore();
const router = useRouter();
const isAuthenticated = computed(() => authStore.isAuthenticated);
const drawer = ref(false)
const display = useDisplay()
const logout = async () => {
try {
await api.post('/logout');
authStore.logout();
router.push('/login');
} catch (error) {
console.error('Logout failed', error);
}
};
</script>
SYT
News
mdi-video
mdi-account
mdi-login
mdi-logout
Β© {{ new Date().getFullYear() }} SYT
add app in footer on mobile devices
set color
### **Modify NProgress Configuration and CSS**
#### **1. Update NProgress Configuration (Optional)**
You can define the **color dynamically** using JavaScript, but it is typically easier to style using CSS.
import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; // Import default styles
// Configure NProgress
NProgress.configure({
showSpinner: false,
speed: 500,
minimum: 0.2
});
—
#### **2. Set Progress Bar Color via CSS**
Modify or create a `nprogress.css` file in `src/assets/` (or include the styles in your global CSS file):
/* Progress Bar */
#nprogress .bar {
background: #ff5733 !important; /* Change to any color you prefer */
}
/* Progress Peg (Shiny Effect) */
#nprogress .peg {
box-shadow: 0 0 10px #ff5733, 0 0 5px #ff5733 !important;
}
/* Remove Spinner */
#nprogress .spinner {
display: none !important;
}
—
#### **3. Import the Custom CSS in `main.js`**
Make sure you **import the custom styles** inside `main.js` (or wherever you initialize Vue).
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import NProgress from 'nprogress';
import '@/assets/nprogress.css'; // Import the custom styles
const app = createApp(App);
app.use(router);
app.mount('#app');
—
### **How It Works**
– The **progress bar color** is now `#ff5733` (orange-red).
– The **shiny effect (peg shadow)** is also set to match the bar color.
– The **spinner is hidden** (`showSpinner: false` is set in JS and hidden via CSS).
Now, when you navigate between pages, you'll see the **custom-colored NProgress bar** at the top! π
app should work on mobile but not on desktop
—
### **Solution:**
Use Vuetifyβs **`useDisplay()`** to check screen size dynamically.
### **Updated Code:**
<template>
<v-app>
<!-- App Bar (Header) -->
<v-app-bar color="primary" dark>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
<RouterLink to="/" style="text-decoration: none; color: inherit;">
<v-toolbar-title>SYT</v-toolbar-title>
</RouterLink>
<v-spacer></v-spacer>
<v-btn :to="`/news`"> News </v-btn>
<v-btn icon :to="`/youtube`">
<v-icon>mdi-video</v-icon>
</v-btn>
<v-btn icon :to="isAuthenticated ? `/dashboard` : `/register`">
<v-icon>mdi-account</v-icon>
</v-btn>
<v-btn icon :to="`/login`" v-if="!isAuthenticated">
<v-icon>mdi-login</v-icon>
</v-btn>
<v-btn icon @click="logout" v-if="isAuthenticated">
<v-icon>mdi-logout</v-icon>
</v-btn>
</v-app-bar>
<!-- Sidebar Navigation (Drawer) -->
<v-navigation-drawer v-model="drawer" temporary>
<v-list>
<v-list-item title="Dashboard" prepend-icon="mdi-view-dashboard" :to="`/dashboard`"></v-list-item>
<v-list-item title="Profile" prepend-icon="mdi-account" :to="`/profile`"></v-list-item>
<v-list-item title="Settings" prepend-icon="mdi-cog" :to="`/settings`"></v-list-item>
<v-list-item title="Posts" prepend-icon="mdi-format-list-bulleted" :to="`/posts`"></v-list-item>
<v-list-item title="Logout" prepend-icon="mdi-logout" @click="logout"></v-list-item>
</v-list>
</v-navigation-drawer>
<!-- Main Content -->
<div class="content-wrapper">
<v-main>
<v-container>
<slot />
</v-container>
</v-main>
<!-- Footer (Sticky on Mobile, Normal on Desktop) -->
<v-footer color="primary darken-3" :app="isMobile">
<v-container class="text-center white--text">
Β© {{ new Date().getFullYear() }} SYT
</v-container>
</v-footer>
</div>
</v-app>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useAuthStore } from '@/stores/auth';
import { useRouter } from 'vue-router';
import { useDisplay } from 'vuetify';
import api from '@/api';
const authStore = useAuthStore();
const router = useRouter();
const isAuthenticated = computed(() => authStore.isAuthenticated);
const display = useDisplay();
const drawer = ref(false);
const isMobile = computed(() => display.width.value <= 767); // Only enable `app` on mobile
const logout = async () => {
try {
await api.post('/logout');
authStore.logout();
router.push('/login');
} catch (error) {
console.error('Logout failed', error);
}
};
</script>
<style scoped>
/* Ensure footer is placed after content and is only sticky on mobile */
.content-wrapper {
display: flex;
flex-direction: column;
min-height: 100vh;
}
v-main {
flex-grow: 1;
}
</style>
—
### **How It Works**
β
**`isMobile` dynamically checks if the width is β€ 767px** and applies `app` only for mobile devices.
β
On **mobile** (`width
Try `npm i –save-dev @types/nprogress` if it exists or add a new declaration (.d.ts) file containing `declare module 'nprogress';`
2 import NProgress from 'nprogress';
### **Solution 1: Install Type Definitions**
Run the following command to install `@types/nprogress`:
npm install --save-dev @types/nprogress
or
yarn add -D @types/nprogress
—
### **Solution 2: Manually Declare the Module**
If the type definitions are not available, you can create a custom type declaration file.
#### **Steps:**
1. Inside your `src` folder, create a new folder `types` (if not already present).
2. Inside `types/`, create a file named **`nprogress.d.ts`**.
3. Add the following content to `nprogress.d.ts`:
declare module 'nprogress' {
const NProgress: any;
export default NProgress;
}
4. Ensure your **`tsconfig.json`** includes the `types` folder:
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./src/types"]
}
}
—
### **Solution 3: Use Dynamic Import (Temporary Workaround)**
If you don't want to deal with TypeScript issues directly, you can import `nprogress` dynamically:
const NProgress = await import('nprogress');
—
### **Final Steps**
– **Restart your development server** after making changes:
npm run dev
or
yarn dev
After following one of the above solutions, TypeScript should stop showing errors related to `nprogress`. π