SvelteKit Dynamic Image Import: Add Images to Templates

Rodney Lab - Sep 20 '21 - - Dev Community

✨ Introduction

Let's start by defining what we mean by SvelteKit dynamic image import by looking at a use case. We'll then run through how to use vite's dynamic import functionality to help us out.

Before we start, I should mention that we are not talking about regular addition of images to a SvelteKit site. For example if you write a Svelte component specifically for a given page. Let's say an About Us page on a company site. If you know at the time you write the component exactly which image will be included on the page, then you probably want to be looking at the article on adding responsive, Next-Gen images in SvelteKit using vite-imagetools.

Here we are looking at cases where the page might be rendered by a template. That template might be responsible for rendering several pages, and each of those pages may well have a different image in the browser. This is the case even though they all come from the same template. An example might be an online store. You could have a template for product pages. Each individual product page can be created by the same temple but will probably include an image of the actual product it is referring to. This is exactly the case we are looking at here. Now you know what this is all about, let's tackle the problem!

πŸ–‹ Example use Case

We'll look at a blog site as our example here. Our template will be able to render any blog post on our site. However to improve user experience and ace Search Engine Optimisation, we decide to add a different featured image to each blog post. We also want to use responsive images, with placeholders to reduce cumulative layout shift. Finally we want to cache and have links for images used in Search Engine Optimisation (SEO) meta data. Let's summarise our needs:

  1. Responsive images, in Next-Gen formats with placeholders to reduce Cumulative Layout Shift,

  2. We have a way to specify which image we will use on each page but need the template to able to render an arbitrary image

  3. Want to use vite-imagetools to do heavy lifting in generating responsive image data.

Let's try to motivate a solution next.

πŸ§‘πŸ½β€πŸŽ“ SvelteKit Dynamic Image Import Problem

With vite-imagetools, you can generate a source url just by importing an asset already saved within the project. As an example, the SvelteKit code might look like this:

import featuredImageSrc from '$lib/assets/about/company-headquarters.jpg';
Enter fullscreen mode Exit fullscreen mode

We could add this code to an about page, for example, though going back to our blog use case, we would need a way to specify the image dynamically. You might already know that dynamic imports are possible with SvelteKit. This functionality is provided by vite. Examples are:

const modules = import.meta.glob('./dir/*.js')
const data = await import(`../lib/assets/blog/${office}/index.json`);
Enter fullscreen mode Exit fullscreen mode

You might include the second example in a template file, defining office in an import.

These work in the general case, though I had issues importing an image using vite-imagetools with a dynamic variable (no issues at all if we use a static path). Here is an example of the dynamic variable case:

// WOULD NOT WORK
const src = await import(`../lib/assets/blog/${slug}/${featuredImage}.jpg`;
Enter fullscreen mode Exit fullscreen mode

Interestingly the following worked fine (importing a JavaScript file dynamically, rather than an image):

imageData = await import(`../lib/assets/blog/data/${slug}.js`;
Enter fullscreen mode Exit fullscreen mode

πŸ§‘πŸ½β€πŸŽ“ SvelteKit Dynamic Image Import Solution

I can smell a potential solution to the SvelteKit dynamic image import problem! We can create one of these files for each blog post and in the file generate the image data as usual. For example add something like this to the data file:

import meta from '$lib/assets/blog/post-one/featured-image.jpg';

const { src, width } = meta;

const data = {
  src,
  width
}

export { data as default };
Enter fullscreen mode Exit fullscreen mode

The downside here is that we have to write one of these files for each blog post. That might be quite onerous to do manually. However, we could create a script to generate these files, one for each blog post. On top we could include extra data like low resolution image placeholders as well as SEO images, generated in our script. The script can run before every build, though in reality only needs to be changes on adding a new blog post or changing the blog images.

Here is an example blog post image data file generated using such a script:

import meta from '$lib/assets/blog/best-medium-format-camera-for-starting-out/best-medium-format-camera-for-starting-out.jpg?width=672&metadata';
import srcsetwebp from '$lib/assets/blog/best-medium-format-camera-for-starting-out/best-medium-format-camera-for-starting-out.jpg?w=1344;672&webp&srcset';
import srcsetauto from '$lib/assets/blog/best-medium-format-camera-for-starting-out/best-medium-format-camera-for-starting-out.jpg?w=1344;672&jpeg&srcset';
import ogImage from '$lib/assets/blog/best-medium-format-camera-for-starting-out/best-medium-format-camera-for-starting-out-open-graph.jpg';
import ogSquareImage from '$lib/assets/blog/best-medium-format-camera-for-starting-out/best-medium-format-camera-for-starting-out-open-graph-square.jpg';
import twitterImage from '$lib/assets/blog/best-medium-format-camera-for-starting-out/best-medium-format-camera-for-starting-out-twitter.jpg';

const { height, src, width } = meta;

const data = {
  slug: 'best-medium-format-camera-for-starting-out',
  alt: 'Photograph of a Hasselblad medium format camera with the focusing screen exposed',
  width,
  height,
  src,
  sources: [
    { srcset: srcsetwebp, type: 'image/webp' },
    { srcset: srcsetauto, type: 'image/jpeg' },
  ],
  dominantColour: '#484848',
  placeholder:
    'data:image/jpeg;base64,/9j/2wBDAAwREhMUFBUUFBofGhUaHiIcGhohKC4jJB4hMzg/PTouKC5CRFpMPi5XRTc3VmBRVlpgZmRkQklveXFmeFtlaWf/2wBDAQgMDQwNDw8PDxFgEBIaVmBaYFpgY2NjY2BjYGBiY2NjY2NjY2NjY2NjY2JjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAHAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAIF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAwX/2gAMAwEAAhADEAAAAM+Cev8A/8QAHBABAAEEAwAAAAAAAAAAAAAAAQIAAxMhEZGh/9oACAEBAAE/AM5u2kZDIkS48rMmtnTX/8QAFhEBAQEAAAAAAAAAAAAAAAAAAQIA/9oACAECAQE/AKtkkDf/xAAXEQEAAwAAAAAAAAAAAAAAAAABABEh/9oACAEDAQE/AHKZ/9k=',
  ogImage,
  ogSquareImage,
  twitterImage,
};

export { data as default };
Enter fullscreen mode Exit fullscreen mode

Using a script, if we decide we want to use AVIF images instead of WebP or change our image widths, it is pretty easy to regenerate the data files. Likewise if we decide to go for a plain, dominant colour placeholder instead of the low resolution one. You can see a full working example in the SvelteKit Blog MDsveX starter on the Rodney Lab GitHub page.

πŸ™πŸ½ Feedback

Have you found the post useful? Do you have your own methods for solving this problem? Let me know your solution. Would you like to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please consider supporting me through Buy me a Coffee.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram. Also, see further ways to get in touch with Rodney Lab. I post regularly on SvelteKit as well as other topics. Also subscribe to the newsletter to keep up-to-date with our latest projects.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .