-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Description
Describe the bug
After updating our template to Svelte 5 we are seeing an excessive amount of hydration markers, way more than in a default SvelteKit project. Our CMS has a hard limit on the size of the HTML strings — before gzip — and with so many markers it is easy to surpass it and be blocked from publishing. With adapter-static, a minimal Svelte 4 HTML payload is 5.7kb. With the same code, a Svelte 5 payload is 6.8kb, an increase of 18%. This change also makes debugging on the browser inspector very difficult.
We observe roughly 25 hydration markers between each node. In a baseline SvelteKit project (https://stackblitz.com/edit/sveltejs-kit-template-default-9a5meubq?description=The%20default%20SvelteKit%20template,%20generated%20with%20create-svelte&file=README.md&title=SvelteKit%20Default%20Template) we only see one marker between each component.
We understand that the hydration markers are part of the built output, as described in #14004 and #14099, but we believe there is something in how we’re rendering our components that is increasing the number of markers to a zany level, and we would be grateful if you could provide any suggestions or guidance on how to mitigate the issue.
At the moment, given the nature of our downstream consumers (iOS and Android applications), this is a blocker for us for moving to Svelte 5 at The New York Times. We’d be open to any suggestions on how best to approach!
Reproduction
Our current approach
Our template works with a body loop. The data comes from an external source and when the type matches a component, it renders. We also have a special component that imports and renders a component declared on a prop (saves you from importing it manually and adding it to the body loop), and it's imported using a +page.js file.
It looks roughly like this:
<script>
import Text from './Text.svelte';
import Header from './Header.svelte';
import Rule from './Rule.svelte';
</script>
{#each data.body as { type, value: props }}
{#if type === 'text'}
<Text {props} />
{:else if type === header}
<Header {props} />
{:else if type === rule}
<Rule />
{:else if type === 'svelte'}
<svelte:component this={props.component} />
{/if}
{/if}
Here's a sample REPL with a small test case where you can see the proliferation of markers. Our template is far more complex and has lots of components (and components inside components), but this illustrates the basic problem:
Dynamic body loop
We also tried switching to a "dynamic" body loop that uses <svelte:component>
to render every element based on an object. We thought this would reduce the markers since there are only two pairs of ifs in the loop. After testing, it does reduce the markers but it is still problematic. We are seeing ~10 markers between each node, which unfortunately it's not really usable either.
<script>
import Code from '$lib/Code/index.svelte';
import DynamicComponent from '$lib/DynamicComponent/index.svelte';
import Header from '$lib/Header/index.svelte';
import Rule from '$lib/Rule/index.svelte';
import Text from '$lib/Text/index.svelte';
const components = {
Code,
DynamicComponent,
Header,
Rule,
Text,
};
export let data;
</script>
<section>
{#each data.body as block}
{#if components[block.type]}
<svelte:component this={components[block.type]} value={block.value} />
{:else}
Missing component
{/if}
{/each}
</section>
The data that renders this looks like this:
{
"type": "Header",
"value": "Svelte 5 hydration markers (dynamic components)"
},
{
"type": "Text",
"value": "In this example every component is imported statically and rendered without a body loop. There are still too many markers between elements."
},
{
"type": "Rule",
"value": ""
},
{
"type": "Text",
"value": "Quis nostrud <i>exercitation</i> ullamco laboris nisi ut aliquip ex ea commodo consequat."
},
{
"type": "DynamicComponent",
"value": "my prop"
},
{
"type": "MyMissingComponent",
"value": "my prop"
}
]
Logs
Here's an example of the markup we're seeing with our current body loop:
<!--[-->
<!--[-->
<!---->
<!---->
<!--[-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[-->
<div style="--g-header-text-wrap: balance;" class="g-header-container g-theme-news g-align-center g-style-default s-WxiNLIoGK6tg"><header class="g-header s-WxiNLIoGK6tg">
<!--[!-->
<!--]-->
<!--[!-->
<!--]-->
<div class="g-heading-wrapper s-WxiNLIoGK6tg"><h1 class="g-heading s--WEV63UuptOE">
<!--34bvmx-->
Lorem Ipsum Dolor!!
<!---->
</h1>
<!---->
</div>
<!--[!-->
<!--]-->
<!--[!-->
<!--]-->
<!--[-->
<div class="g-byline-wrapper s-WxiNLIoGK6tg"><p class="g-byline s-oyjqywUozi8_">
<!--[!-->
<!--]-->
<!--[!-->
<!--[!-->
<span class="g-byline-prefix s-oyjqywUozi8_">By</span> <span itemprop="name" class="g-last-byline s-oyjqywUozi8_">The New York Times</span>
<!--]-->
<!--]-->
<!--[!-->
<!--]-->
</p>
<!---->
<span class="g-timestamp-wrapper s-WxiNLIoGK6tg"><time class="g-interactive-timestamp s-w590EQv4ALB0 " datetime="2024-12-02T16:30:17-05:00">
<!--[!-->
<!--[!-->
<!--[-->
Dec. 2, 2024
<!--]-->
<!--]-->
<!--]-->
</time>
<!---->
</span></div>
<!--]-->
<!--[!-->
<!--]-->
<!--[!-->
<!--]-->
<!--[!-->
<!--]-->
</header></div>
<!---->
<!---->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[!-->
<!--[-->
<!--[-->
<!--[!-->
<!---->
<p class="g-text s-BKgJCsuAs_Ng">
<!--[-->
<!--om3kqk-->
Nisi aliquip mollit aliqua non in in, mollit in qui id enim reprehenderit adipiscing ea. Minim sit, tempor consequat occaecat ut sed reprehenderit in dolore cillum laboris culpa irure. Nulla amet sint do dolore ut quis eu officia minim in esse.
<!---->
<!--]-->
<!---->
</p><!---->
<!--]-->
<!--]-->
<!---->
<!---->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!--]-->
<!---->
<!---->
<!---->
<!--]-->
<!--[!-->
<div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
<!---->
</div>
<!--]-->
<!--]-->
And this is what we see with the dynamic body loop:
<!--[-->
<!--[-->
<!---->
<!---->
<!--[-->
<!--[-->
<!---->
<div class="g-header-container g-theme-news g-align-center g-style-default s-WxiNLIoGK6tg" style="--g-header-text-wrap:balance">
<header class="g-header s-WxiNLIoGK6tg">
<!--[!-->
<!--]-->
<!--[!-->
<!--]-->
<div class="g-heading-wrapper s-WxiNLIoGK6tg">
<h1 class="g-heading s--WEV63UuptOE">
<!--34bvmx-->
Lorem Ipsum Dolor
<!---->
</h1>
<!---->
</div>
<!--[!-->
<!--]-->
<!--[!-->
<!--]-->
<!--[-->
<div class="g-byline-wrapper s-WxiNLIoGK6tg">
<p class="g-byline s-oyjqywUozi8_">
<!--[!-->
<!--]-->
<!--[!-->
<!--[!-->
<span class="g-byline-prefix s-oyjqywUozi8_">
By</span>
<span itemprop="name" class="g-last-byline s-oyjqywUozi8_">
The New York Times</span>
<!--]-->
<!--]-->
<!--[!-->
<!--]-->
</p>
<!---->
<span class="g-timestamp-wrapper s-WxiNLIoGK6tg">
<time class="g-interactive-timestamp s-w590EQv4ALB0 " datetime="2025-01-31T14:03:50-05:00">
<!--[!-->
<!--[!-->
<!--[-->
Jan. 31, 2025<!--]-->
<!--]-->
<!--]-->
</time>
<!---->
</span>
</div>
<!--]-->
<!--[!-->
<!--]-->
<!--[!-->
<!--]-->
<!--[!-->
<!--]-->
</header>
</div>
<!---->
<!---->
<!--]-->
<!--[-->
<!---->
<!--[-->
<!--[!-->
<!---->
<p class="g-text s-BKgJCsuAs_Ng">
<!--[-->
<!--1r2ynjb-->
hello<!---->
<!--]-->
<!---->
</p>
<!---->
<!--]-->
<!--]-->
<!---->
<!---->
<!--]-->
<!--[-->
<!---->
<!--[-->
<!--[!-->
<!---->
<p class="g-text s-BKgJCsuAs_Ng">
<!--[-->
<!--u0i1n6-->
Et consequat laborum commodo aliqua eu in adipiscing incididunt ut, tempor amet ullamco dolore. Ex sunt mollit sunt ut veniam est dolore magna.<!---->
<!--]-->
<!---->
</p>
<!---->
<!--]-->
<!--]-->
<!---->
<!---->
<!--]-->
<!--]-->
<!---->
<!---->
<!---->
<!--]-->
<!--[!-->
<div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
<!---->
</div>
<!--]-->
<!--]-->
System Info
System:
OS: macOS 14.7.2
CPU: (10) arm64 Apple M1 Pro
Memory: 361.97 MB / 32.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 22.13.1 - ~/.nvm/versions/node/v22.13.1/bin/node
npm: 10.9.2 - ~/.nvm/versions/node/v22.13.1/bin/npm
pnpm: 7.33.2 - ~/Library/pnpm/pnpm
Browsers:
Chrome: 132.0.6834.160
Safari: 18.2
Severity
blocking an upgrade