You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Vue should provide a way to hydrate an SSR'ed component without calling mount(parentElementThatContainsMyComponent).
I explain the reasons why in the "Use Case" heading below.
I am not proposing a specific API change right now so that the discussion is open to a variety of solutions.
Use Case
I want to use Vue to render entire HTML pages server-side, and then hydrate only the few components that truly require it on the front-end, i.e. the Islands Architecture.
This has several benefits, including:
You can ship a smaller JS bundle to the front-end, easily omitting any SSR'ed components that only exist to produce HTML on the back-end
You can have true server-side components that contain server-only code. This separation means you don't need to rely on tree-shaking/variables/if-statements to check the context, and you're less likely to accidentally include sensitive server-side code in your front-end JS bundle via a configuration error.
Only the JS needed for interactive/front-end components runs on users devices, which can improve the user's experience
The Problem
The current hydration API requires you to "hydrate" an entire Vue app by passing an element whose contents are the app/components to hydrate.
You can get around this by treating each component island as a separate Vue app, which is ok.
Here's the problem: When you render the island/component on the server, you must wrap the island/component in an extra <div> or other parent element so that you can pass that parent element to app.mount() for hydration.
This is not good for the Islands use case for a few reasons:
It clutters the markup with unnecessary, un-semantic elements, making it harder to read and understand
Not all islands/components are block-level elements, so you have to add a prop or something to your island wrapper that allows you to specify what kind of wrapping element you want to render
The root element of your island is not the component root.
What if, for example, you want your component to be a list item (<li>)? Then you're stuck with markup like this:
<ul><div><li>Content</li></div></ul>
True, you could make the island wrapper an <li>, but now your component is no longer really a list item, just the contents of a list item. That's a more complicated/less-useful abstraction, and it makes it much harder to share components in, say, a shared library where consumers may not be using SSR. That, or you'll need to include some kind of <ClientIsland> component as the root in each client-side component.
Some frameworks like Nuxt (see [1], [2], and [3]) and Astro work around this limitation by rendering a wrapper element and styling it with display: contents to make it act as if it weren't there.
However, as noted above, they commonly use display: contents to workaround the issue I described. That's unacceptable (see above for why), and especially so for large websites/apps that serve many users. It also adds complexity and clutters the markup with unnecessary wrapper elements.
Nuxt, Astro, and frameworks like them are not always the right fit for a project. It's necessary at times to use Vue alone.
Why not wrap all of your islands with a reusable <ClientIsland> component and hydrate them as individual Vue apps?
That's what I'm doing now, and it has all of the downsides outlined above. This is a request for a feature/solution that would address these downsides.
Could you re-arrange the DOM after mounting the front-end component?
I tried an experiment where I render the wrapping <div> on the server-side, mount the Vue app to it on the client-side, then discard the wrapping <div> and replace it with its own contents (the elements rendered by the Vue app/component itself).
In my brief testing, this didn't seem to cause any errors.
However, this is a misuse of the API and seems likely to have undefined consequences later. You're effectively lying to Vue about what element the app is mounted to by swapping it out after mounting.
Existing RFCs
This request is similar to 242 and 728, but I opened this RFC to provide more details and because 728 seemed more focused on getting help with the existing implementation.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Feature Request
Vue should provide a way to hydrate an SSR'ed component without calling
mount(parentElementThatContainsMyComponent)
.I explain the reasons why in the "Use Case" heading below.
I am not proposing a specific API change right now so that the discussion is open to a variety of solutions.
Use Case
I want to use Vue to render entire HTML pages server-side, and then hydrate only the few components that truly require it on the front-end, i.e. the Islands Architecture.
This has several benefits, including:
The Problem
The current hydration API requires you to "hydrate" an entire Vue app by passing an element whose contents are the app/components to hydrate.
You can get around this by treating each component island as a separate Vue app, which is ok.
Here's the problem: When you render the island/component on the server, you must wrap the island/component in an extra
<div>
or other parent element so that you can pass that parent element toapp.mount()
for hydration.This is not good for the Islands use case for a few reasons:
<li>
)? Then you're stuck with markup like this:<li>
, but now your component is no longer really a list item, just the contents of a list item. That's a more complicated/less-useful abstraction, and it makes it much harder to share components in, say, a shared library where consumers may not be using SSR. That, or you'll need to include some kind of<ClientIsland>
component as the root in each client-side component.Some frameworks like Nuxt (see [1], [2], and [3]) and Astro work around this limitation by rendering a wrapper element and styling it with
display: contents
to make it act as if it weren't there.However, using
display: contents
in this way is buggy and famously bad for accessibility.Alternatives Considered
Why not just use Nuxt / Astro / etc.?
I have, and they are fine for some use cases.
However, as noted above, they commonly use
display: contents
to workaround the issue I described. That's unacceptable (see above for why), and especially so for large websites/apps that serve many users. It also adds complexity and clutters the markup with unnecessary wrapper elements.Nuxt, Astro, and frameworks like them are not always the right fit for a project. It's necessary at times to use Vue alone.
Why not wrap all of your islands with a reusable
<ClientIsland>
component and hydrate them as individual Vue apps?That's what I'm doing now, and it has all of the downsides outlined above. This is a request for a feature/solution that would address these downsides.
Could you re-arrange the DOM after mounting the front-end component?
I tried an experiment where I render the wrapping
<div>
on the server-side, mount the Vue app to it on the client-side, then discard the wrapping<div>
and replace it with its own contents (the elements rendered by the Vue app/component itself).In my brief testing, this didn't seem to cause any errors.
However, this is a misuse of the API and seems likely to have undefined consequences later. You're effectively lying to Vue about what element the app is mounted to by swapping it out after mounting.
Existing RFCs
This request is similar to 242 and 728, but I opened this RFC to provide more details and because 728 seemed more focused on getting help with the existing implementation.
Beta Was this translation helpful? Give feedback.
All reactions