-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vue is not catching errors on server side in SSR when using async child setup #12575
Comments
I did some research and it turns out Calling If we remove connections to Also in the picture you can see that when compiling on server-side Vue calls the child async setup and when it is done it stops compilation. Error is also detected, but after module is built! So it seems Vue on server side can't catch an error and renders the child's template (if it can do it). And only on client side it actually can catch an error, stop child component from executing and display correct error message. But this causes hydration mismatch... |
onErrorCaptured
is not triggered in SSR when async child throws
The error threw in core/packages/runtime-core/src/component.ts Lines 884 to 891 in 833f9ea
Therefore, regardless of whether the setup is executed successfully or not (whether there is a correct setupState ), the component will be rendered:core/packages/server-renderer/src/render.ts Lines 117 to 119 in 833f9ea
If the |
Well this behaviour is really confusing and right now I don't have even an idea of how to catch async child errors in parent wrapper... I tried All these solutions just fail because Vue on server-side tries to render template with broken setup state first, which 99% will cause more unhandled errors inside So I desperately need either a fix or workaround 🥲 The only ugly workaround-like is to write child |
The example without Suspense and SSR, catch the error in It seems like there are different behaviors in different situations, which one is expected? @edison1105 |
@noootwo Can you please give me an example with Suspense (which is turned on by default in Nuxt on root level) and SSR enabled? I tried to create a workaround yersterday for hours and failed. I am rather new in Vue. |
@Gwynerva Can you use |
Unfortunately, the whole point in my library is that wrapper handles the error and adds some handy UI, while child component can be almost anything: KaTeX formula, image, paragraph and etc. Child components are external, often provided by other libraries. And if something goes wrong it is a wrapper that has to catch any errors and display nice error message. Handling errors inside child component eliminates one of main purposes of the wrapper — providing same error design no matter what wrapper is rendering inside. I tried to pass In my situation handling errors in child is a bad practice, because children are supposed "by design" to throw if something goes wrong so wrapper could nicely tell user about a problem in unified way. Observable block system is a good example of what I am trying to do with Vue. |
Sounds you can do like this, but this is just temporary measures. After this issue is resolved, there should be more humanized approaches available. |
Still not applicable because it requires handling the error inside the component where is was thrown. And I plan to have like a few dozens of such "child" components, and all of them can pontentially throw... I really need to handle errors in wrapper, and this wrapper is a "physically" different component, outside of child component. The only possible connections would be possible with <Wrapper> // Part of base library, provides info about <Child> and also catches its errors
<Child /> // Can be an external, can throw, can do anything...
</Wrapper>
<Wrapper>
<Child />
</Wrapper>
<Wrapper>
<Child />
</Wrapper> Anyways, thank you, I think your example will do for testing purposes for now to prevent the whole page crashing on every error. But this is unfortunately not a workaround :( This seems like a rather serious SSR issue to me because right now |
These two examples capture the same error messages. Below are the modified versions, please observe the console.log When wrapped inside Suspense, the error message is not displayed because of |
The original issue arises because we catch exceptions during client-side rendering execution at
core/packages/server-renderer/src/render.ts Line 192 in bf3d9a2
|
I would argue about "minor" because in my opinion using standart Either way, it's nice to know it's not a problem on my end. Very much looking forward to the fix, as there is no way to workaround it and the bug is extremely annoying. |
@Gwynerva |
@edison1105 Does not seem to work or I am doing something wrong? Also, is there a way to use your PR instead of |
Oh, I made a mistake npm i https://pkg.pr.new/vue@12601 |
@edison1105 This does not work with bun, but I installed it in different folder and just copy/pasted the new files inside my project. Now, it does not crash the whole page (which is super cool) but still tries to render template causing error and hydration mismatches. As you can see the first error is the error I throw. But then it errors again trying to access non-existing properties of failed |
@Gwynerva |
@edison1105 Sadly, I can't use your PR on Vue SFC Playground. But I made a StackBlitz demo with your PR and reproduced the same example as in first post. Right now it does not even catching errors in sync Comp setup (with Promise line commented). And still not working with async Comp setup (when Promise line is uncommented). Expected behaviour: both sync and async Comp throw calls must result in showing div with "Error: Back to App.vue" text and NOT even try to render Comp because it's |
@edison1105 Now it is working with async Comp and not working with sync Comp (try commenting and uncommenting To be more precise, it actually "kind of" works, but instead of my own error message "Back to App.vue" it shows an error from render when accessing unexisting properties, meaning it for some reasons still tries to render |
I couldn't see where the sync component fails to work. It works fine in both SSR and non-SSR. I am doing something wrong? The error message changed is expected: <Suspense>
<div v-if="error">{{ error }}</div>
<Comp v-else :foo="'Foo Is Here!'" />
</Suspense>
|
@edison1105 Why sync Comp.vue calling both setup and render? Isn't sync version is supposed to trigger parent I want to show only actual error, the error in Looks totally not safe for me to supress errors... |
When calling the setup function of a component
Regardless of whether the component is synchronous or asynchronous, the exception thrown in the |
Ok, I see now. So I guess my solution here is to display div with the first error only and supress everything after. |
@Gwynerva if the tag is not a p, it will work as expected.see |
I will investigate this and let you know. Thank you! |
@edison1105 Sadly, still get hydration mismatches when rendering nodes deep inside Also, can't check the direct SSR output in Vue Playground but on my Nuxt setup it simply does not render the error message, just putting blank This is how I render block in my library: <script lang="ts" setup>
// ...
onErrorCaptured(e => {
if (error.value)
return false;
console.error(`Error in block product "${props.node.name}"!`);
console.error(e);
error.value = e;
return false;
});
</script>
<template>
<div :class="cls.blockContainer">
<BlockFloat v-bind="{ ...props, ...{ position: 'above' } }" />
<template v-if="error">
<div :class="[cls.block, cls.error]">
<BlockAside v-bind="props" />
<div :class="cls.blockMain">{{ error }}</div>
</div>
</template>
<template v-else>
<ProductComponent v-if="render.customLayout" />
<div v-else :class="cls.block">
<BlockAside v-bind="props" />
<div :class="cls.blockMain">
<ProductComponent />
</div>
</div>
</template>
<BlockFloat v-bind="{ ...props, ...{ position: 'below' } }" />
</div>
</template> My guess is that Vue SSR or Nuxt don't see an error and try to render That is how I get |
@Gwynerva see Playground |
@edison1105 Does this mean there are no ways to do component replacement in parent when async child throws with Suspense + SSR? I tried to use nested
But this supression will allso ignore problems with badly written child components, not allowing to detect possible SSR errors in them. So I guess this attribute should be added only when building site for production. |
I think so. |
Such a long road to such a painful failure, lol. Well, I think my only option here is to redesing some core functionality. |
Vue version
3.5.13
Link to minimal reproduction
Vue SFC Playground
Steps to reproduce
Just open a link and you will see errors when SSR is ON.
When SSR is OFF everything works as expected.
Also, commeting the line with top-level await (making
Comp.vue
a sync component) will also work fine both with and without SSR.What is expected?
In SSR mode the page is expected to be blank white for 1 second and then display error message from
App.vue
.This works as expected with SSR turned off.
Basically
onErrorCaptured
should be triggered, child setup and<template>
render immediately stopped and error message from parent component shown.What is actually happening?
In SSR mode the page is blank for 1 second but then it tries to render the
<template>
ofComp.vue
using the data that might not even exist (because it appears insetup
after throwing error).And even if it manages to render
<template>
correctly, it still does not triggeronErrorCaptured
inApp.vue
and do not switch to show error message.Any additional comments?
I am building a custom rendering package built on top of Vue and it heavily uses structures like these:
Because of this bug OR my misunderstanding of how to handle async errors in Vue 3, even when thrown it still tries to render child block's
<template>
even with missing data (because setup is stopped executing), throws on reading properties of non-existing objects and the whole Nuxt page breaks.Please, don't tell me this is a no-fix. It would be a disaster... 😭
The text was updated successfully, but these errors were encountered: