Custom error.vue only appears in development (Nuxtv4)
Hello, I am developing an app with Nuxtv4 and I made the
error.vue
at ~ along with app.vue
, now in development it appears just fine. Also when I do pnpm generate
and pnpm preview
it shows yet in the actual build it shows the default one instead of the custom one
I have this in its script
I have tried
- The commented out props definition
- Use <slot /> instead of <NuxtPage> inside <NuxtLagout> in app.vue which doesn't work and in fact gives error that I have pages and not using NuxtPage
- Having <slot /> outside of <NuxtLayout> in app.vue
- Setting createError as fatal: true in both API routes, server middleware (None of my pages have createError)
- Removing all directories and doing build and still it shows default instead of custom error.vue in production
Now what should I do? It literally does show the custom one in dev but doesn't in production after all these14 Replies
Hi @Red. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
----------------------
A few things to check so your custom error.vue renders in production:
- Ensure error.vue is at the project root (same level as app.vue), not in pages, and accepts the error prop exactly as documented: defineProps<{ error: NuxtError }>(). It’s not a route; don’t use definePageMeta inside it (error.vue doc).
(Example and fields provided here, including statusCode, statusMessage, data) (error.vue doc). - Wrap your error page content with a layout using <NuxtLayout>, not NuxtPage. If you need a layout, do it inside error.vue: Don’t use <NuxtPage> in layouts for error rendering; use <slot /> in layouts so error.vue can render properly (Pages and Layouts migration, NuxtLayout API). - Make sure you’re actually triggering a “fatal” error in production. Nuxt shows the full-screen error page only for fatal/unhandled errors. On the client, use createError({ fatal: true }) to force the error page; server-side unhandled errors are fatal by default (Error Page, useError / createError). - If you rely on statusMessage or data from server errors, recent fixes ensure Nuxt passes error.statusMessage and error.data to error.vue even if Nitro’s production JSON omits them. Upgrade to a Nuxt version that includes these fixes (PR: pass error.data, PR: pass statusMessage). - Don’t use definePageMeta in error.vue, and don’t expect virtual routing. Use clearError({ redirect: '/' }) to exit the error state (Error Handling: Error Page). - For 404s on static hosts: if you’re generating and deploying statically, ensure the platform serves your SPA fallback (e.g., 404.html or redirect to index.html). Otherwise you might see the host’s 404 instead of Nuxt’s error page (Stack Overflow case). If after these checks production still shows the default page, it likely means: - The deployed build doesn’t include your error.vue (verify it’s in the source root and rebuild). - The error is not considered fatal/unhandled in production (log and confirm throwing createError({ fatal: true }) where you test). - Your layout uses <NuxtPage> instead of <slot />, blocking error.vue rendering (migration note). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
(Example and fields provided here, including statusCode, statusMessage, data) (error.vue doc). - Wrap your error page content with a layout using <NuxtLayout>, not NuxtPage. If you need a layout, do it inside error.vue: Don’t use <NuxtPage> in layouts for error rendering; use <slot /> in layouts so error.vue can render properly (Pages and Layouts migration, NuxtLayout API). - Make sure you’re actually triggering a “fatal” error in production. Nuxt shows the full-screen error page only for fatal/unhandled errors. On the client, use createError({ fatal: true }) to force the error page; server-side unhandled errors are fatal by default (Error Page, useError / createError). - If you rely on statusMessage or data from server errors, recent fixes ensure Nuxt passes error.statusMessage and error.data to error.vue even if Nitro’s production JSON omits them. Upgrade to a Nuxt version that includes these fixes (PR: pass error.data, PR: pass statusMessage). - Don’t use definePageMeta in error.vue, and don’t expect virtual routing. Use clearError({ redirect: '/' }) to exit the error state (Error Handling: Error Page). - For 404s on static hosts: if you’re generating and deploying statically, ensure the platform serves your SPA fallback (e.g., 404.html or redirect to index.html). Otherwise you might see the host’s 404 instead of Nuxt’s error page (Stack Overflow case). If after these checks production still shows the default page, it likely means: - The deployed build doesn’t include your error.vue (verify it’s in the source root and rebuild). - The error is not considered fatal/unhandled in production (log and confirm throwing createError({ fatal: true }) where you test). - Your layout uses <NuxtPage> instead of <slot />, blocking error.vue rendering (migration note). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@1129158996146782309> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@kapa.ai Are you missing some context from my message or something?
- I already mentioned that error.vue is NOT in pages and accepts the error prop and I even gave you the start of it's script, do you see
definePageMeta
inside it? It is at the same level as app.vue
- I specifically mentioned app.vue
when talking about <slot />, <NuxtLayout /> and <NuxtPage />. I don't use any of these three in error.vue. This is my app.vue
I have tried replacing NuxtPage with slot in above and it still does NOT render the custom error page
- Yes I just said I trigger a fatal error
- I am on the latest Nuxt v4 version
- I am not deploying staticallyLet me check the knowledge base for you again. One moment! :books:
----------------------
Thanks for clarifying. Given you’re on Nuxt v4, non-static deployment, and your error.vue is correctly placed and typed, two production-only gotchas remain likely:
1) Ensure your layout renders error.vue via slots
- In Nuxt 3/4, layouts must render children with <slot/>, not <NuxtPage/>. If your active layout still uses <NuxtPage/>, error.vue won’t render. Replace <NuxtPage/> with <slot/> in the layout used in production (not in app.vue). This is a common cause where error.vue is ignored when wrapped with a layout (Pages and Layouts migration, NuxtLayout API).
- Your app.vue is fine using <NuxtLayout><NuxtPage/></NuxtLayout>, but your layout file itself must use <slot/> (not <NuxtPage/>) so the error page can be slotted in (NuxtPage). 2) Verify the error reaching error.vue is “fatal” and carries fields in prod - Full-screen error page renders only for fatal/unhandled errors. Server errors are fatal; client errors must be thrown with fatal: true (which you said you do) (Error Page, useError/createError).
- In production, Nitro’s default error response omits statusMessage/data, but Nuxt passes them to error.vue in recent versions. Ensure your version includes these fixes; otherwise your UI logic depending on them may fall back to the default template: pass error.data/statusMessage to error.vue was added in these PRs (pass error.data, pass statusMessage). The error.vue prop shape is documented here (error.vue doc). Minimal checklist: - In the layout used at runtime, replace any <NuxtPage/> with <slot/> (migration note).
- Keep app.vue as you have it (<NuxtLayout><NuxtPage/></NuxtLayout>) (NuxtLayout API).
- Throw createError({ fatal: true, statusCode, statusMessage, data }) and confirm it reaches error.vue in prod (Error Page).
- In error.vue, accept props as defineProps<{ error: NuxtError }>() and use clearError({ redirect: '/' }) to exit (error.vue doc). If that still shows the default page, it indicates the active layout at deploy time still uses <NuxtPage/> somewhere in its render path, preventing error.vue from slotting in (same root cause as the SO fix) (SO: default layout vs error.vue). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
- Your app.vue is fine using <NuxtLayout><NuxtPage/></NuxtLayout>, but your layout file itself must use <slot/> (not <NuxtPage/>) so the error page can be slotted in (NuxtPage). 2) Verify the error reaching error.vue is “fatal” and carries fields in prod - Full-screen error page renders only for fatal/unhandled errors. Server errors are fatal; client errors must be thrown with fatal: true (which you said you do) (Error Page, useError/createError).
- In production, Nitro’s default error response omits statusMessage/data, but Nuxt passes them to error.vue in recent versions. Ensure your version includes these fixes; otherwise your UI logic depending on them may fall back to the default template: pass error.data/statusMessage to error.vue was added in these PRs (pass error.data, pass statusMessage). The error.vue prop shape is documented here (error.vue doc). Minimal checklist: - In the layout used at runtime, replace any <NuxtPage/> with <slot/> (migration note).
- Keep app.vue as you have it (<NuxtLayout><NuxtPage/></NuxtLayout>) (NuxtLayout API).
- Throw createError({ fatal: true, statusCode, statusMessage, data }) and confirm it reaches error.vue in prod (Error Page).
- In error.vue, accept props as defineProps<{ error: NuxtError }>() and use clearError({ redirect: '/' }) to exit (error.vue doc). If that still shows the default page, it indicates the active layout at deploy time still uses <NuxtPage/> somewhere in its render path, preventing error.vue from slotting in (same root cause as the SO fix) (SO: default layout vs error.vue). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@1129158996146782309> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@kapa.ai I am testing with server errors only, not client ones. Also, I don't use layouts in the first place how do I replace it? Not only that isn't error.vue not accepting of any layouts in the first place?
Let me check the knowledge base for you again. One moment! :books:
----------------------
- Server errors (from API routes, plugins during SSR, etc.) trigger the Nuxt error page automatically if they are unhandled/fatal. That error page is rendered via ~/error.vue when present (Error Page).
- error.vue can use a layout. It is not a route, but you may wrap its content in a layout using the NuxtLayout component inside error.vue itself (NuxtLayout API, error.vue guide). Example:
Note: layouts themselves must render their children with <slot/>, not <NuxtPage/>, otherwise error.vue can’t slot in (Pages and Layouts migration).
- If your server error is thrown from Nitro server files (server/api, server/middleware), it won’t “go through Vue” until Nuxt renders error.vue. That’s expected; JSON responses may omit some fields in production, but Nuxt passes statusMessage and data through to error.vue in recent versions. Ensure you’re on a version including these fixes so your error UI doesn’t fall back unexpectedly (pass error.data, pass statusMessage; error fields listed in docs: statusCode, statusMessage, data, etc. (error.vue guide)).
- Summary answers to your questions:
- “I’m testing server errors only”: That’s fine—unhandled server errors should render error.vue in production too (Error Page).
- “I don’t use layouts—how do I replace it?”: You don’t need a layout. If you do want one for error.vue, wrap error.vue’s template with <NuxtLayout name="...">. You don’t configure this elsewhere; it’s inside error.vue (NuxtLayout API).
- “Isn’t error.vue not accepting layouts?”: It does accept layouts via <NuxtLayout> inside error.vue (it’s not a route, but you can still use layouts that way) (error.vue guide).
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
<@1129158996146782309> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@kapa.ai Ok I wrapped it in a layout and used slot, kept the NuxtPage as is in app.vue and it still renders the default in production
Let me check the knowledge base for you again. One moment! :books:
----------------------
Thanks for the extra detail. Given you’re throwing server (Nitro) errors and seeing Nuxt’s default error page only in production, these are the concrete checks that are documented:
- error.vue location/shape: must be at project root (alongside app.vue) and accept the error prop; you already have this right (error.vue guide). If you want a layout, wrap your error UI in <NuxtLayout> inside error.vue; layouts themselves must render with <slot/> (not <NuxtPage/>) (error.vue guide, layouts migration note).
- Production propagation of server error fields: in production, Nitro’s default error response omits statusMessage/data, but Nuxt passes them to error.vue when rendering it. Ensure your Nuxt version includes these fixes; otherwise your error.vue may not get the expected data and could look like the default page (pass error.data, pass statusMessage). The documented error shape is statusCode, fatal, unhandled, statusMessage?, data?, cause? (error.vue guide).
- Server vs client rendering path: unhandled server errors during Nitro lifecycle do render the full-screen error page (or JSON if Accept: application/json). This is the same mechanism used in production (Error Page).
If all above is correct and you still see the default error page only in production, the docs don’t provide another knob to force error.vue over the default. That suggests either:
- the deployed build is not picking up your error.vue (e.g., different root, monorepo/workspace packaging), or
- something in production prevents Nuxt from rendering error.vue (e.g., request has Accept: application/json, which returns JSON instead of the page) (Error Page).
To isolate:
- Confirm the failing request is HTML (no Accept: application/json) and reproduces via direct browser navigation in prod (Error Page).
- Log in server to ensure the error is unhandled (not caught and rethrown as a plain JSON/Response).
- Verify the built output includes your error.vue and that your active layout uses <slot/> (if you wrapped error.vue with a layout) (error.vue guide, layouts migration).
I’m sorry there isn’t a further production-only switch documented; beyond the items above, the knowledge sources don’t mention additional causes.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
<@1129158996146782309> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@kapa.ai It is returning HTML and in the API endpoint I'm using createError with statusCode and message. I have done direct browser navigation too
Let me check the knowledge base for you again. One moment! :books:
----------------------
Given it’s an HTML request, server error, and you’re on Nuxt v4 with a correct error.vue, two documented production-specific causes remain:
- Nitro server errors don’t go “through Vue” until Nuxt decides to render error.vue; ensure you’re not in server middleware trying to customize that screen—those are Nitro-handled and won’t use Vue unless Nuxt renders error.vue afterward. If you want a Vue-rendered error, throw via route middleware or in the Vue rendering path (Nitro vs route middleware).
- In production, Nitro may omit message fields in API responses, but Nuxt passes statusMessage/data into error.vue during rendering only in versions with these fixes. Double‑check you’re on a build that includes them; otherwise your UI might look like the default because your props are missing expected data (pass error.data, pass statusMessage, error.vue fields). What you can verify next (documented knobs are limited): - Confirm the error is unhandled during server render (plugins/SSR/page data) or thrown from an API route using createError; those should trigger the full-screen error page (not JSON) and render error.vue for HTML requests (Error Page, createError in API routes).
- Ensure your error.vue uses the documented prop signature and can render with minimal markup to rule out data dependence: (error.vue guide) If after this you still get the default page only in production, the knowledge sources don’t document another switch to force error.vue. That would point to a deployment/build discrepancy (monorepo root, different sourceDir) or an environment where the error is handled at the Nitro layer before Nuxt renders error.vue. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
- In production, Nitro may omit message fields in API responses, but Nuxt passes statusMessage/data into error.vue during rendering only in versions with these fixes. Double‑check you’re on a build that includes them; otherwise your UI might look like the default because your props are missing expected data (pass error.data, pass statusMessage, error.vue fields). What you can verify next (documented knobs are limited): - Confirm the error is unhandled during server render (plugins/SSR/page data) or thrown from an API route using createError; those should trigger the full-screen error page (not JSON) and render error.vue for HTML requests (Error Page, createError in API routes).
- Ensure your error.vue uses the documented prop signature and can render with minimal markup to rule out data dependence: (error.vue guide) If after this you still get the default page only in production, the knowledge sources don’t document another switch to force error.vue. That would point to a deployment/build discrepancy (monorepo root, different sourceDir) or an environment where the error is handled at the Nitro layer before Nuxt renders error.vue. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@1129158996146782309> Kapa.ai is still learning and improving, please let me know how I did by reacting below.