N
Nuxt2mo ago
stan

using dependencies in plugins

I am trying to use "vue3-toastify" in nuxt plugin that i am using as global error handler but i am not seeing the toast popped up. What can be the reason for this? The console.log shows up correctly so we know the error is reaching here.
import { toast } from "vue3-toastify";

export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
console.log("[Nuxt] Error: ", error);
toast.error(`[Nuxt] Error: ${error}`);
};
});
import { toast } from "vue3-toastify";

export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
console.log("[Nuxt] Error: ", error);
toast.error(`[Nuxt] Error: ${error}`);
};
});
11 Replies
kapa.ai
kapa.ai2mo ago
Hi @stan. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- There could be several reasons why your toast notifications aren't appearing despite the error handler being triggered. Let me help you troubleshoot this issue. Plugin Registration Order One common issue is that the plugin that provides the toast functionality might not be loaded before your error handler plugin. In Nuxt, plugins are loaded in alphabetical order, so you might need to ensure your toast plugin loads first:
// plugins/01.vue3-toastify.ts
import { toast } from "vue3-toastify";
import 'vue3-toastify/dist/index.css'; // Don't forget to import the CSS

export default defineNuxtPlugin(() => {
return {
provide: {
toast
}
}
});

// plugins/02.error-handler.ts
export default defineNuxtPlugin((nuxtApp) => {
const { $toast } = useNuxtApp();

nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
console.log("[Nuxt] Error: ", error);
$toast.error(`[Nuxt] Error: ${error}`);
};
});
// plugins/01.vue3-toastify.ts
import { toast } from "vue3-toastify";
import 'vue3-toastify/dist/index.css'; // Don't forget to import the CSS

export default defineNuxtPlugin(() => {
return {
provide: {
toast
}
}
});

// plugins/02.error-handler.ts
export default defineNuxtPlugin((nuxtApp) => {
const { $toast } = useNuxtApp();

nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
console.log("[Nuxt] Error: ", error);
$toast.error(`[Nuxt] Error: ${error}`);
};
});
Plugin Dependencies Alternatively, you can use the object syntax for plugins with the dependsOn property to explicitly define dependencies between plugins:
// plugins/vue3-toastify.ts
import { toast } from "vue3-toastify";
import 'vue3-toastify/dist/index.css';

export default defineNuxtPlugin({
name: 'vue3-toastify',
setup() {
return {
provide: {
toast
}
}
}
});

// plugins/error-handler.ts
export default defineNuxtPlugin({
name: 'error-handler',
dependsOn: ['vue3-toastify'],
setup(nuxtApp) {
const { $toast } = useNuxtApp();

nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
console.log("[Nuxt] Error: ", error);
$toast.error(`[Nuxt] Error: ${error}`);
};
}
});
// plugins/vue3-toastify.ts
import { toast } from "vue3-toastify";
import 'vue3-toastify/dist/index.css';

export default defineNuxtPlugin({
name: 'vue3-toastify',
setup() {
return {
provide: {
toast
}
}
}
});

// plugins/error-handler.ts
export default defineNuxtPlugin({
name: 'error-handler',
dependsOn: ['vue3-toastify'],
setup(nuxtApp) {
const { $toast } = useNuxtApp();

nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
console.log("[Nuxt] Error: ", error);
$toast.error(`[Nuxt] Error: ${error}`);
};
}
});
This ensures that your error handler plugin waits for the toast plugin to be fully initialized before running Creating Plugins. Additional Considerations 1. Make sure you've properly set up the toast container component in your app. Some toast libraries require you to add a container component to your app layout. 2. Check if you need to initialize the toast library with Vue. For example, with PrimeVue's toast service:
// plugins/primevue-toastservice.ts
import ToastService from 'primevue/toastservice'

export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(ToastService);
});
// plugins/primevue-toastservice.ts
import ToastService from 'primevue/toastservice'

export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(ToastService);
});
As shown in this Stack Overflow answer. 3. Verify that the CSS for the toast library is properly imported. 4. Try using the toast directly in a component to see if it works there, which would help isolate whether the issue is with the error handler or with the toast setup. If you're still having issues, you might want to consider using Nuxt UI's built-in notification system which is designed to work seamlessly with Nuxt Components. Let me know if any of these solutions work for you! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@549277605354930199> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
stan
stanOP2mo ago
@kapa.ai Thanks That works correctly. I have another question . In this code
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
baseURL: "https://api.voucher.et/api/v1/",
onRequest({ request, options, error }) {},
async onResponseError({ request, response }) {
try {
const responseBody = await response.text();
const errorDetails = {
status: response.status,
statusText: response.statusText,
body: JSON.parse(responseBody),
message: JSON.parse(responseBody)?.message || "Unknown error",
timestamp: new Date().toISOString(),
};

nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
} catch (parseError) {
const errorDetails = {
status: response.status,
statusText: response.statusText,
message: "API Error",
timestamp: new Date().toISOString(),
};

nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
}
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
baseURL: "https://api.voucher.et/api/v1/",
onRequest({ request, options, error }) {},
async onResponseError({ request, response }) {
try {
const responseBody = await response.text();
const errorDetails = {
status: response.status,
statusText: response.statusText,
body: JSON.parse(responseBody),
message: JSON.parse(responseBody)?.message || "Unknown error",
timestamp: new Date().toISOString(),
};

nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
} catch (parseError) {
const errorDetails = {
status: response.status,
statusText: response.statusText,
message: "API Error",
timestamp: new Date().toISOString(),
};

nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
}
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
why am i getting response.json is already read error
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can see why you're encountering the "response.json is already read" error in your Nuxt plugin. This is a common issue when working with fetch responses in JavaScript. The Problem The error occurs because you're trying to read the response body with response.text() after the response body has already been consumed. In the Fetch API (which $fetch is based on), response bodies can only be read once. Once you've called methods like .json() or .text(), the body is consumed and can't be read again. This typically happens in Nuxt's $fetch implementation when: 1. The response body is automatically read internally by $fetch/ofetch 2. You then try to read it again with response.text() Solution There are a few ways to solve this issue: Option 1: Use response._data In Nuxt's $fetch implementation (which uses ofetch), the parsed response is already available as response._data:
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
baseURL: "<https://api.voucher.et/api/v1/",>
onRequest({ request, options, error }) {},
async onResponseError({ request, response }) {
try {
// Use response._data instead of parsing the body again
const errorDetails = {
status: response.status,
statusText: response.statusText,
body: response._data,
message: response._data?.message || "Unknown error",
timestamp: new Date().toISOString(),
};

nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
} catch (error) {
const errorDetails = {
status: response.status,
statusText: response.statusText,
message: "API Error",
timestamp: new Date().toISOString(),
};
nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
}
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
baseURL: "<https://api.voucher.et/api/v1/",>
onRequest({ request, options, error }) {},
async onResponseError({ request, response }) {
try {
// Use response._data instead of parsing the body again
const errorDetails = {
status: response.status,
statusText: response.statusText,
body: response._data,
message: response._data?.message || "Unknown error",
timestamp: new Date().toISOString(),
};

nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
} catch (error) {
const errorDetails = {
status: response.status,
statusText: response.statusText,
message: "API Error",
timestamp: new Date().toISOString(),
};
nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
}
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
This approach is demonstrated in a similar context in a Stack Overflow answer where they access response._data directly in the onResponse handler Stack Overflow Post > Http GET request via Pinia action only works on second attempt in Nuxt app Part 3. Option 2: Use interceptors properly If you need more control, you can use the interceptor pattern as shown in the Nuxt documentation:
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
baseURL: "<https://api.voucher.et/api/v1/",>
onResponseError({ request, response, options }) {
const errorDetails = {
status: response.status,
statusText: response.statusText,
message: response._data?.message || "API Error",
timestamp: new Date().toISOString(),
};

nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
baseURL: "<https://api.voucher.et/api/v1/",>
onResponseError({ request, response, options }) {
const errorDetails = {
status: response.status,
statusText: response.statusText,
message: response._data?.message || "API Error",
timestamp: new Date().toISOString(),
};

nuxtApp.vueApp.config.errorHandler(new Error(errorDetails.message));
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
Additional Notes 1. Be careful with reactive properties in the request body, as they can cause multiple requests to be sent, as mentioned in this Stack Overflow post. 2. If you're creating a custom fetcher, you might want to check out the Custom useFetch in Nuxt documentation for best practices. 3. For proper error handling in Nuxt, you might also want to look at using createError or showError utilities as described in the Error Handling documentation. Let me know if this resolves your issue! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@549277605354930199> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
stan
stanOP2mo ago
@kapa.ai I have this oni-init plugin that i want to run on application creation (when it starts)
import { useAuthService } from "#imports";

export default defineNuxtPlugin((nuxtApp) => {
const useAuth = useAuthService();

nuxtApp
.$api("app/auth/refresh", {
method: "POST",
credentials: "include",
})
.then(async (data) => {
useAuth.setAccessToken(data.accessToken);
});
});
import { useAuthService } from "#imports";

export default defineNuxtPlugin((nuxtApp) => {
const useAuth = useAuthService();

nuxtApp
.$api("app/auth/refresh", {
method: "POST",
credentials: "include",
})
.then(async (data) => {
useAuth.setAccessToken(data.accessToken);
});
});
and i have profile.vue that has this getProfile request call
const {
data,
status,
execute: getProfile,
error: profileError,
} = await useAPI("app/auth/profile", {
immediate: false,
credentials: "include",
});

onMounted(async () => {
await getProfile();
});
const {
data,
status,
execute: getProfile,
error: profileError,
} = await useAPI("app/auth/profile", {
immediate: false,
credentials: "include",
});

onMounted(async () => {
await getProfile();
});
now the problem is even tho, getProfile needs an access token that comes from the refresh call it ends up running at the same time with out any access token from the refresh
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can see the issue you're facing with the execution order of your requests. Let me explain what's happening and how to fix it. The Problem Your plugin is making an asynchronous request to refresh the auth token, but it's not waiting for this request to complete before the application continues loading. This means your getProfile request in the component might execute before the token refresh is complete. Solution: Properly Handle Plugin Dependencies You need to ensure that your auth token is available before making API requests that require it. Here are a few approaches: Option 1: Use the onNuxtReady composable
// plugins/on-init.js
import { useAuthService } from "#imports";

export default defineNuxtPlugin((nuxtApp) => {
const useAuth = useAuthService();

// Return a promise that resolves when the token is refreshed
return nuxtApp
.$api("app/auth/refresh", {
method: "POST",
credentials: "include",
})
.then(async (data) => {
useAuth.setAccessToken(data.accessToken);
})
.catch(error => {
console.error("Failed to refresh token:", error);
});
});
// plugins/on-init.js
import { useAuthService } from "#imports";

export default defineNuxtPlugin((nuxtApp) => {
const useAuth = useAuthService();

// Return a promise that resolves when the token is refreshed
return nuxtApp
.$api("app/auth/refresh", {
method: "POST",
credentials: "include",
})
.then(async (data) => {
useAuth.setAccessToken(data.accessToken);
})
.catch(error => {
console.error("Failed to refresh token:", error);
});
});
The defineNuxtPlugin function can return a promise, and Nuxt will wait for this promise to resolve before continuing with the application initialization Stack Overflow Post > How to reinitiate/resend a request with $fetch like Axios?. Option 2: Check for token in your API requests Modify your useAPI composable to check if the token is available and wait for it if needed:
// In your profile.vue
onMounted(async () => {
const useAuth = useAuthService();

// Wait for token to be available before making the request
if (!useAuth.getAccessToken()) {
await new Promise(resolve => {
const checkToken = setInterval(() => {
if (useAuth.getAccessToken()) {
clearInterval(checkToken);
resolve();
}
}, 100);
});
}

await getProfile();
});
// In your profile.vue
onMounted(async () => {
const useAuth = useAuthService();

// Wait for token to be available before making the request
if (!useAuth.getAccessToken()) {
await new Promise(resolve => {
const checkToken = setInterval(() => {
if (useAuth.getAccessToken()) {
clearInterval(checkToken);
resolve();
}
}, 100);
});
}

await getProfile();
});
Option 3: Use plugin dependencies If you have multiple plugins that depend on each other, you can use the object syntax with dependsOn to ensure proper execution order:
// plugins/auth-refresh.js
export default defineNuxtPlugin({
name: 'auth-refresh',
async setup(nuxtApp) {
const useAuth = useAuthService();

try {
const data = await nuxtApp.$api("app/auth/refresh", {
method: "POST",
credentials: "include",
});
useAuth.setAccessToken(data.accessToken);
} catch (error) {
console.error("Failed to refresh token:", error);
}
}
});

// plugins/api-setup.js
export default defineNuxtPlugin({
name: 'api-setup',
dependsOn: ['auth-refresh'],
setup(nuxtApp) {
// This will only run after auth-refresh is complete
}
});
// plugins/auth-refresh.js
export default defineNuxtPlugin({
name: 'auth-refresh',
async setup(nuxtApp) {
const useAuth = useAuthService();

try {
const data = await nuxtApp.$api("app/auth/refresh", {
method: "POST",
credentials: "include",
});
useAuth.setAccessToken(data.accessToken);
} catch (error) {
console.error("Failed to refresh token:", error);
}
}
});

// plugins/api-setup.js
export default defineNuxtPlugin({
name: 'api-setup',
dependsOn: ['auth-refresh'],
setup(nuxtApp) {
// This will only run after auth-refresh is complete
}
});
This ensures that any plugin that depends on the auth token will wait for the refresh to complete Creating Plugins. Additional Considerations 1. SSR vs. Client-side: If you're using SSR, be aware that plugins run on both server and client. You might want to use process.client or add .client.js suffix to your plugin filename to ensure it only runs on the client side. 2. Error Handling: Make sure to add proper error handling for the token refresh to prevent your application from getting stuck if the refresh fails. 3. Token Storage: Consider using a more persistent storage method for your tokens (like cookies or localStorage) to avoid having to refresh on every page load. Let me know if you need any clarification or have additional questions! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@549277605354930199> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
stan
stanOP2mo ago
@kapa.ai how should routing work from inside plugin.
export default defineNuxtPlugin((nuxtApp) => {
const router = useRouter();

const api = $fetch.create({
baseURL: "http://localhost:3000/api/v1/",
onRequest({ request, options, error }) {
const useAuth = useAuthService();

if (useAuth.token()) {
options.headers = {
...options.headers,
"x-access-token": useAuth.token(),
};
}
},
async onResponseError({ request, response }) {
const responseBody = await response._data;

if (
response.status == 401
) {
await nuxtApp.runWithContext(() =>
navigateTo("/auth/login?redirect_from_app=true"),
);
router.push("/auth/login?redirect_from_app=true");
return;
}
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
export default defineNuxtPlugin((nuxtApp) => {
const router = useRouter();

const api = $fetch.create({
baseURL: "http://localhost:3000/api/v1/",
onRequest({ request, options, error }) {
const useAuth = useAuthService();

if (useAuth.token()) {
options.headers = {
...options.headers,
"x-access-token": useAuth.token(),
};
}
},
async onResponseError({ request, response }) {
const responseBody = await response._data;

if (
response.status == 401
) {
await nuxtApp.runWithContext(() =>
navigateTo("/auth/login?redirect_from_app=true"),
);
router.push("/auth/login?redirect_from_app=true");
return;
}
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
why doesnt the nuxtApp.runWithContext run correctly when the app starts.
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can see you're having an issue with navigation in your Nuxt plugin, specifically when handling 401 unauthorized responses. Let me explain what's happening and how to fix it. The Issue with runWithContext The problem you're experiencing is related to how Nuxt handles context in asynchronous operations. When you're using nuxtApp.runWithContext() in an async callback like onResponseError, you might encounter issues with the Nuxt context being lost or unavailable, especially during application initialization. This is a known limitation in Nuxt when working with complex async/await scenarios in plugins and middleware, as mentioned in the Nuxt documentation. Why It's Not Working There are a few reasons why your navigation might not be working correctly: 1. Context Loss: During async operations, the Nuxt context can be lost, which is why runWithContext exists in the first place. 2. Duplicate Navigation: You're using both navigateTo and router.push which might be causing conflicts. 3. Timing Issues: When the app starts, some components of Nuxt might not be fully initialized yet. Solution Here's how to fix your plugin:
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
baseURL: "<http://localhost:3000/api/v1/",>
onRequest({ request, options, error }) {
const useAuth = useAuthService();
if (useAuth.token()) {
options.headers = {
...options.headers,
"x-access-token": useAuth.token(),
};
}
},
async onResponseError({ request, response }) {
if (response.status == 401) {
// Use only one navigation method
return nuxtApp.runWithContext(() =>
navigateTo("/auth/login?redirect_from_app=true")
);
}
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
export default defineNuxtPlugin((nuxtApp) => {
const api = $fetch.create({
baseURL: "<http://localhost:3000/api/v1/",>
onRequest({ request, options, error }) {
const useAuth = useAuthService();
if (useAuth.token()) {
options.headers = {
...options.headers,
"x-access-token": useAuth.token(),
};
}
},
async onResponseError({ request, response }) {
if (response.status == 401) {
// Use only one navigation method
return nuxtApp.runWithContext(() =>
navigateTo("/auth/login?redirect_from_app=true")
);
}
},
credentials: "include",
});

return {
provide: {
api,
},
};
});
Key changes: 1. Removed the duplicate router.push call 2. Made sure to return the result of navigateTo 3. Simplified the error handling Additional Considerations If you're still experiencing issues, you might want to try one of these approaches: 1. Server vs Client Navigation: For server-side navigation issues, you can use a conditional approach as shown in this Stack Overflow answer:
if (response.status == 401) {
if (import.meta.server) {
const response = requestEvent.node.res;
if (!response.headersSent) {
response.writeHead(302, {Location: '/auth/login?redirect_from_app=true'});
response.end();
}
} else {
return nuxtApp.runWithContext(() =>
navigateTo("/auth/login?redirect_from_app=true")
);
}
}
if (response.status == 401) {
if (import.meta.server) {
const response = requestEvent.node.res;
if (!response.headersSent) {
response.writeHead(302, {Location: '/auth/login?redirect_from_app=true'});
response.end();
}
} else {
return nuxtApp.runWithContext(() =>
navigateTo("/auth/login?redirect_from_app=true")
);
}
}
2. Plugin Order: Make sure your plugin runs at the right time by using the naming convention (e.g., 01.api.js) or the object syntax with dependencies. 3. Error Handling: Add proper error handling and logging to debug any issues. The runWithContext method is specifically designed for scenarios like yours where the Nuxt context might be lost in async operations, so it's the right approach, but it needs to be implemented carefully. Let me know if this helps or if you need further clarification! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@549277605354930199> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?