Why my image is not hydrating correctly when Svelte initializes

Preface

I am writing a Shopify theme using

liquivelte
which is in essence liquid rendered html being hydrated with svelte.

I have added support for the

image_tag filter
which outputs a img tag so it is not a input output case for svelte side. Normally how I handle liquid filter conterpart on svelte side is like this. Let's say there is a image_url filter

<img src="{{- somevalue | image_url: width: 300 -}}" >
liquivelte

liquivelte converts it to

<img src="{ liquid.image_url(somevalue, {width: 300}) }">
svelte

This is fine for input output case scenarios. However image_tag is different. One might say this is a more complex filter. Then I moved forward with adding an exception for image_tag and it became like this. Lets say we have a image tag expression:

<div class="image-container">
  {{- product.images[0] | image_url: width: 300 | image_tag }}
</div>
liquivelte

It would output

<div class="image-container">
  <img {...liquid.image_tag( liquid.image_url(product.images[0], {width: 300}), {} ) } />
</div>
svelte

I thought why not, lets add image_tag filter as a utility function and spread props that come out of it. I did not see what was coming.

The Problem

I realized images are loading correctly, however there was a flashing. Images would reload. This is a no no, this means large layout shifts, this means bad UX. But what was wrong, I checked network panel for single image and it was loading exactly same image 2 times. Here is I am comparing 2 urls thinking it got to be different. image url comparison

I went to svelte issues and spent some time on this

better hydration issue
but I was thinking "this is about iframes and stuff, images are so essential for web it would be much more fuss it this was happening on images all the time". Then I went back to trustworthy devtools to find my way around. Started debugging and added a debugger; statement at the end of image statement as an inline script. That would stop html parsing at that point so I know at that point initial image is there. At this point I went to "elements" panel and added (DOM breakpoints)[
https://developer.chrome.com/docs/devtools/javascript/breakpoints/#dom
] to the element itself to find out which function is replacing or modifying the element. ss of adding dom breakpoints

Indeed I found the function who replaces the node and adds a new one. It is a core svelte function called claim_element_base. function with name claim_element_base

This function is checking if existing element has attributes of the svelte counterpart of that element. If it does not have an attribute it mark it for removal.

This is what gpt has to say about it: gpts word on the function

Conclusion

At the end of the day answer to question of this article is; if you use props spreading in svelte, your elements will get destroyed and re-created when hydrating. I now converted the conversion logic to use attributes one by one and it works like a charm.

When there is a expression in liquid side with image tag I convert it to

<div>
  <img src="{liquid.image_url(product.images[0], {width: 300}), {} ) }" srcset="..." alt="..." />
</div>
svelte

instead of prop spreading like this:

<div class="image-container">
  <img {...liquid.image_tag( liquid.image_url(product.images[0], {width: 300}), {} ) } />
</div>
svelte

Outro

I am not someone with obsession to address layout shift issues. Layout shifts are unacceptable in todays more and more competitive web development industry. They are the one of best known UX spoilers and we should eliminate them at whichever cost I believe.

Doing so it more and more easy nowadays. With the death of IE and browser vendors adopting improvements faster than ever, you should at least ship code with

aspect-ratio
to ensure thing do not jump around when page is loading or styles are applying consecutively.

Related Topics

If you care I would suggest learning these concepts for a performance competitive or UX acceptable web page.