Subscribe on changes!

defineAsyncComponent render error in ssr

avatar
Feb 10th 2023

This could be a bug on the playground, currentInstance = null that's not possible, did you test it in the test case?

avatar
Feb 10th 2023

This could be a bug on the playground, currentInstance = null that's not possible, did you test it in the test case?

https://github.com/vuejs/core/blob/78b86150b801e7eb4ebf1cdff977e791dc0438f0/packages/runtime-core/__tests__/hydration.spec.ts#L716 This test is passed, maybe it is a playground bug

avatar
Aug 11th 2023

Hey, it is definitely not a playground bug, I have run into the exact same issue with our production application.

Here is some more context:

  • It is an SSR app built on https://vite-plugin-ssr.com/ with Vue 3.3.4
  • We have noticed that sometimes if the Async component chunks were loading slower on flaky networks (and always in Safari for some reason), the hydration of components was failing, but was succeeding after the chunks had been cached on subsequent page visits.

I would say as the issue naturally occurs in Safari, even on stable networks, it is quite severe.

After looking into the implementation for defineAsyncComponent I have noticed that the case of hydration is never handled when the component is not managed by a Suspense. https://github.com/vuejs/core/blob/623ba514ec0f5adc897db90c0f986b1b6905e014/packages/runtime-core/src/apiAsyncComponent.ts#L198

       if (loaded.value && resolvedComp) {
          return createInnerComp(resolvedComp, instance)
        } else if (error.value && errorComponent) {
          return createVNode(errorComponent, {
            error: error.value
          })
        } else if (loadingComponent && !delayed.value) {
          return createVNode(loadingComponent)
        } 
         
         // If none of the above conditions are met, meaning that the component is still loading 
         // and the component was rendered on the server, then no vnode is returned resulting
        // in a `<!--[-->`(Fragment) being rendered and hydration mismatch in return

Workaround: Wrap each AsyncComponent in the template with Suspense

<template>
  <Suspense>
    <AsnycComp />
  </Suspense>
</template>

<script setup>
const AsyncComp = defineAsyncComponent(() => import('./Comp.vue'));
</script>