Closed happycollision closed 1 month ago
A while back we decided to not hydrate @html
comments to check them for mismatches because it's a slow untertaking and very rare in general that someone would want this. If we maintainers still agree on that change then we
As a workaround you can reassign the HTML in onMount, like
<script>
import { onMount } from "svelte"
let renderLocation = "On server"
onMount(() => { renderLocation = "In browser"; });
</script>
<h1>Welcome to SvelteKit</h1>
Standard: {renderLocation}
<br />
@html: {@html renderLocation}
What's your real-life use case for this behavior?
In my case, I suppose the onMount functionality would allow me to do what I need.
I am running a site (postplayhouse.com) that is statically generated, but some of that information needs to change based on what today’s date is. We had a problem the other day where there was a mismatched title against a description, because I used Markdown (hence the @html
) for the description. It was a very surprising thing to discover and I couldn’t figure it out for a while, so I just regenerated the whole site so the SSR version was actually no longer out of date.
I think adding documentation in both the upgrade guide and also in the section of the docs that discuss @html
would have prevented me from shipping this bug. Seems like a good plan.
Here's some more context to describe my use case a little more clearly, in case there is something in it that is compelling for a behavior change.
<-- routes/(app)/+page.svelte -->
<script>
import Markdown from "$components/Markdown.svelte"
import {daysUntil} from "$utils"
import messages from "some_data_connection"
const today = new Date()
const daysLeft = daysUntil(today, "2024-07-12")
const msg = daysLeft < 5 ? messages.endingSoon : messages.nowRunning
</script>
<Markdown source={msg}/>
It is inside the Markdown component that we use the {@html source}
business. ~So I'm guessing I'd need to do this:~ (Edit: this actually does NOT work. See my comment further down. I found a working solution there)
<script lang="ts" context="module">
import { marked } from "marked"
+ import { onMount } from "svelte"
marked.setOptions({ smartypants: true })
</script>
<script lang="ts">
let { source }: { source?: string } = $props()
+ let hydratedSource = $state(source)
+
+ onMount(() => {
+ hydratedSource = source
+ })
</script>
<div class="via-markdown">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
- {@html marked.parse(source || "")}
+ {@html marked.parse(hydratedSource || "")}
</div>
I guess in this case, it is kind of weird, because the props going into the component change, which you'd think would do the trick, so maybe call out in the docs as well that if you are using {@html props.something}
, you'll still not see an update on hydration unless your props actually are changed once after hydration is complete.
Maybe that'd be obvious to others. In any case, I am guessing a Markdown component is common enough a use case that you'd perhaps see this pop up frequently in that kind of scenario. 🤷
Oh. Silly. Even my diff above doesn't work (because the state doesn't change after hydration... it's just the first render still). This actually works:
<script lang="ts" context="module">
import { marked } from "marked"
import { onMount } from "svelte"
marked.setOptions({ smartypants: true })
</script>
<script lang="ts">
let { source }: { source?: string } = $props()
let markdown = $derived(marked.parse(source || ""))
let hydrated = $state(false)
onMount(() => {
hydrated = true
})
</script>
<div class="via-markdown">
{#if hydrated}
{@html markdown}
{:else}
{@html markdown}
{/if}
</div>
If you are curious and want to dig in to the scenario, here's the branch where I am replicating what we saw in production:
https://github.com/postplayhouse/postplayhouse.com/tree/test-brokenness
The site was SSG'd on June 23 (which I am mocking using mockdate
inside the vite config), then the site was rendered on July 4th (which I am mocking using timemachine
in app.html
). Inside the Markdown component, I was trying to understand what was happening, so I modified it to spit out multiple strings instead of just the rendered HTML. You'll see on render, they don't match.
Since Post Playhouse is a non-profit, I try to keep spending on anything to a minimum. Hence the fact that I rely on SSG for nearly everything, and just replace the content with JS in the client instead of on the server. I use it as a replacement for all kinds of date-based announcements that'd often be handled server-side. Mildly bad for SEO, but good for costs and still being able to set-and-forget.
Anyway. I hope you all have some more insight into this use case. Love the changes in Svelte 5!
There's various ways to approach this and the best solution depends on your use case - your solution is valid!
If you know the HTML stays mostly the same and it's only a small fraction that is dynamic, you could also try to split that out.
Describe the bug
Using
{@html someVar}
in Svelte 5, I noticed that hydration does not overwrite the SSR value. For example (and this is in the reproduction repo), consider this component in a SvelteKit app:I would expect that after hydration, the page would say:
But instead, it says:
Reproduction
https://github.com/happycollision/svelte-hydration-issue
pnpm i
.npm run dev
in one, check the output of the root route.In the svelte 4 version, you'll see this:
In the svelte 5 version, you'll see this:
Logs
No response
System Info (svelte4)
System Info (svelte5)
Severity
blocking an upgrade