svelte
svelte copied to clipboard
Empty comment in HTML breaks hydration
Describe the bug
The Problem
I'm trying to implement a manual hydration for some parts of my application. Which means I need to render components to a string and pass them to @html to avoid Svelte hydrating this parts of my application automatically. E.g.
<script>
import { render } from "svelte/server";
</script>
<!-- svelte-ignore hydration_html_changed -->
{@html import.meta.env.SSR ? render(someComponent).body : " "}
The problem is that render(someComponent).body contains empty comments (<!---->) for its future hydration. And you get a mismatch because these comments are misinterpreted as the end of @html block.
Possible Solution
Make sure that @html block are closed with a "hash comment" and ignore every other comment. This change should be as simple as replacing (next).data !== '' with (next).data !== hash here. If this is an acceptable solution, I can make a PR.
Use Case
My use case might seem weird to you. I've posted more details on discord. We could discuss it there if needed.
Reproduction
Minimal reproduction:
{@html "<!---->"}
Observe:
hydration_mismatch:
Hydration failed because the initial UI does not match what was rendered on the server
Logs
No response
System Info
System:
OS: macOS 14.6.1
CPU: (8) arm64 Apple M1
Memory: 135.31 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 23.1.0 - /opt/homebrew/bin/node
npm: 10.9.0 - /opt/homebrew/bin/npm
pnpm: 9.4.0 - ~/Library/pnpm/pnpm
bun: 1.1.31 - ~/.bun/bin/bun
Browsers:
Brave Browser: 130.1.71.123
Safari: 17.6
npmPackages:
svelte: ^5.0.0 => 5.0.2
Severity
annoyance
The hash is only added during dev to give you the runtime error in case of mismatch and will not be there in production.
But also, considering that what you are trying to achieve is purely client side why do you need this?
Hmm... So, there are no hashes in prod... Could it be a good idea to make comments defining @html block different from the others anyway (even without hashes)?
While most of what I need is client-side, it still operates on DOM coming from the server (CSR-only is unacceptable in my use case). Since Svelte doesn't have partial (component level) hydration yet, using the @html and render() is the only way I've found to opt out of hydration for a piece of code. Here is a small demo of what I have achieved so far: REPL.
The ultimate goal of this is to preserve state (including DOM references and any modifications done outside of Svelte) between route navigations. E.g. imagine a tab-view where each tab is its own route with SSR, but when navigating between them on a client you keep all the state for each tab. Unfortunately, snapshot API or using layouts is too limiting. This is my I'm making this.
The importance of keep-alive like behaviour has already been discussed here. This solution would also be a possible workaround for https://github.com/sveltejs/svelte/issues/12895 - REPL, since it uses global IDs for component caching.
I've made a full-stack tab-view demo. Note the content!.body.replaceAll("<!---->", "<!----->") in KeepAlive.svelte. The replace is a workaround for the problem described in this issue. I'm not sure how safe it is to do that. Is it OK for hydration to accept any comment or should it always have data = ""? From my testing this workaround kind of works so far. I'm not sure, though. Maybe I'm missing something.
I wonder if the correct solution is that we have a better API for these cases, see https://github.com/sveltejs/svelte/issues/14337.
https://github.com/sveltejs/svelte/issues/14337 would solve this issue.
Unfortunately, this issue is just a part of a problem for my use-case. There are some other problems with my current implementation. For example, any transitions inside KeepAlive wound't work (since components aren't technically mounted). Ideally such the functionality should be provided on a framework level instead of being hacked on top in userland. https://github.com/sveltejs/svelte/issues/6040 was closed waiting for an RFC. I guess I can draft one if you think this is the right direction. Otherwise if we want to keep this in userland, we need more advanced APIs for these use cases (e.g. see https://github.com/sveltejs/svelte/issues/6942#issuecomment-2481085214). I think @Rich-Harris mentioned that he has ideas for a new transition API. I'm curious if they could align with our goals here.
I wonder if the correct solution is that we have a better API for these cases, see #14337.
I don't think #14337 solves this use case because of the intention to hydrate the ssr-d output (to avoid svelte-controlled unmount). #14337 would help with purely server components with no interactivity or just static email markup.
This issue is purely on the hydration mismatch warning (supposedly in dev only) when used in rendering components via the server-side render() api function, placing the output in {@html } and then calling hydrate() on the component load on the client-side.
After speaking to @Rich-Harris about this, we feel the correct fix for this is to make it so the @html block can handle comments inside the content without hydration breaking. This would involve adding starting and ending comments to the content for the raw block.