vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.97k stars 33.68k forks source link

Uncaught DOMException error with Vue SSR production mode using v-if and unpredictable data #7221

Closed Meesayen closed 6 years ago

Meesayen commented 6 years ago

Version

2.5.9

Reproduction link

https://github.com/Meesayen/vue-ssr-uncaught-error-demo

Steps to reproduce

Given the repo, just run npm run build and then npm start (the issue is only reproducible with production mode) and then hit refresh a bunch of times until you get the error in the console (unfortunately the nature of the bug has to do with the unpredictability of some data and its difference between client and server, so I opted for a Math.random() based scenario, hence the need to hit refresh a few times to reproduce the issue)

Highlight of the problematic code:

<template>
  <div id="app">

    <!-- using a v-if to add remove dom nodes according to some unpredictable data -->
    <div v-if="theRandom === true">
      Hello random
    </div>

  </div>
</template>
<script>
  export default {
    name: 'app',

    created() {
      // Having unpredictable data to differ between client and server
      this.theRandom = Math.random() > 0.5
    }

  }
</script>

What is expected?

The app shouldn't explode, instead it should somehow quarantine the error so that everything else will continue to work, and only print an error in console.

What is actually happening?

The app encounters a runtime uncaught DOMException, and Vue stops working completely.


I'm sorry if this has been addressed already, I tried to search the issues but I only found some loosely similar ones that were just closed with no action due to missing informations on how to reproduce the bug or something like that.

I may understand that this particular problem may not be a "bug" per se, maybe it's a known issue that cannot be fixed, hence the warning during the development mode that says to watch out for data/dom mismatch.

But it's something that can really byte you hard, and I'm here because of that. In my case the error only happened in production, live to customers, and it was not really possible to foresee (it depended on some data being different from client to server and only in some specific timezones, which was quite fun to debug).

What I'm asking is if it is possible to make this error less disruptive so that if a single component has this error not the whole will stop to work but just that component.

I will also understand if your answer will be something on the line of "just stop using v-if, and use v-show instead"; that is probably what I'm going to do now, but it's unfortunate that something so useful is also so dangerous and it should maybe be pointed out in some documentation.

One last thing: the warning during development is not quite right,

[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render

albeit useful in most cases, it doesn't represent the actual error we encountered (a simple v-if in the wrong place).

Thanks to whomever will look into this šŸ™šŸ»

LinusBorg commented 6 years ago

One last thing: the warning during development is not quite right,

The warning is absolutely right: Since your DOM structure is relying on random data, the DOM that was generated on the server and sent to the client differs from the DOM that the client-rendered virtualDOM represents/expects to find during hydration. Hence, the app runs into an error.

I'm not sure what other hint you would expect. If you hope for something like

"your v-ifcondition on the server had another value than it has on the client",

...then I have to disappoint you, that would require some kind of "AI" analysing your component's render function code and interpreting it intelligently. Not possible.

So now with that understanding, on to the issue you are facing. The point is: this is expected behaviour and pretty much unavoidable. Hydration can only succeed reliably if all data responsible for defining the DOM is 100% the same on the client and the server.

If you have a component that has something "random" about it like your example: Don't render it on the server at all, and skip the first render on the client.

A nice example of how to easily do this can be found here: https://github.com/egoist/vue-no-ssr

Otherwise, I would try and find a way to skip the "random" part in your data generation on first render and instead use the data that was created on the server, e.g. by storing it in vuex.

Meesayen commented 6 years ago

The "random" was to emphasize the error and make it easier to reproduce, but this can happen with any kind of external data that for some unforeseeable reason may differ from server to client.

What I'm concerned about is the uncaught error; I understand that the client/server data mismatch is something to avoid, but why not just bail out and re-render like it's done in development mode instead? Or isolate the error in the component that is failing?

See, if this problem is inside a small component 15 levels deep in an app structure, I would expect only that component to fail and not render, instead it just makes the whole app stop working.

If at least this could be avoidable, that'd be great. Otherwise, sorry for the waste of time.

LinusBorg commented 6 years ago

It's not that easy. During hydration, the whole app is rendered first, then attached to the DOM. So the trot does not happen inside the component, really.

And if we wanted to check if the dom node for every component is really where it belongs, performance suffers - and performance is the whole point of SSR. If we lose valuable ms during hydration because we verify every piece of the dom, we can stop doing ssr.

That's why we have warnings in development, but not production.

byalexandrepedrosa commented 4 years ago

I have a similar scenario on nuxt.

In my case, I'm trying to replicate the wordpress admin bar ...

Using the simple:

<template v-if = "!authenticated">
Options for unlogged user
</template>
<template v-if = "authenticated">
Options for logged user
</template>

Works ...

But, as the menus vary according to the page (edit post for example, should be view only in an post page), I need a much larger set of validations, so I did these validations in a method.

When I call the method with the necessary validations, example:

<template v-if = "showMenu (items)">
Show button
</template>

Variable items come from a FOR on Navigation Bar and showMenu returns true or false, the error of:

"The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <`p>, or missing . Bailing hydration and performing full client -side render. "

In the Vue documentation it is said to never use complex validations on the template body...

There's any workaround?

PS.: The method get the current object on FOR from Navigation Bar to make the validations. My initial guess would be use computed (if I understood corectly the documentation, this one have data before hydration and would avoid this issue) instead method, but since computed does not accept parameters, I don't know any other way to archive it.

ricky11 commented 3 years ago

I solved my problem by using a v-show. This creates an invisible dom node. Not the best solution, but the only way I could add the node (or make it visible) during hydration.