Imre Gera
April 19, 2025
Reworking the album viewer
Development

This is a slightly technical post, but also a heads-up about a change in the ways albums behave on the site. Photography is the main focus of this website, and building a great photo viewing experience that works equally well on mobile and desktop is a huge challenge. The main issue I’ve run into is scalability: the number of albums and photos on the site is growing, so the old way of doing things was becoming unwieldy. I had to find a way to make it easier to add new albums and photos while also improving the viewing experience.

If you want to try the new experience, click on this picture and start navigating left and right, or click in the middle to zoom in and then pan around the image. On mobile, you can swipe to scroll:

One of my recent photos from Kungsträdgården that I'm proud of.
One of my recent photos from Kungsträdgården that I'm proud of.

The underlying architecture

To give you a bit of context, let’s walk through how the website is built. Every page you see is a static HTML page generated from a number of things: Markdown files for these blog posts, descriptor (YAML) files and photos for each of the albums, and the templates that tie it all together with the help of Astro. Whenever I make changes to the website, it needs to be rebuilt, generating the HTML pages for all the albums and blog posts. This is a great way to build websites that don’t have a lot of dynamic (frequently changing) content, because it makes them really fast and also easy (and in most cases free) to host.

The downside of this architecture is that adding a new album or blog post requires rebuilding the entire site. While blog posts are done quickly, the majority of the build time is spent on generating the albums and the optimized images within the albums. This is what I attempted to improve upon with the changes, also upgrading the image viewing experience a bit while I was there.

The old ways

In the beginning, it made sense to have a separate page for each photo in an album. That is, each photo had its own URL, like imregera.dev/photography/25-04-15-kungstradgarden/3, and thus its own HTML page. This was great because it was easy to build: I didn’t need to worry about navigating between photos (there was a single photo to display), and I could just link to the next photo in the album.

The problems began when I started adding more albums: the build process crashed with an out-of-memory error due to the number of HTML pages it had to generate (again, one for each photo in an album). Now, it’s easy to work around this issue by simply increasing the memory limit, but it’s not a long-term solution: I wanted to able to build the website quickly on my own machine, too. So I started looking for a way to reduce the number of HTML pages that needed to be generated.

One page to rule them all

The solution to this problem was quite simple: instead of generating a separate HTML page for each photo, I could just generate one HTML page for the entire album. This would mean that I would have to load all the photos in the album on that one page and handle the navigation between them on the client side, along with all the other challenges (like preloading the next picture, not downloading all the pictures at once, etc.). But Astro is not really built for interactive content, so I used its island architecture to build a “slideshow” component in Svelte that would do all the heavy lifting.

This itself has two major challenges:

Let’s take a look at the differences in architecture before we dive into these problems.

Old one:

photography/
├─ [album]/
│  ├─ index.astro   <- photography/album-name
│  ├─ [photo].astro <- photography/album-name/image-number

New one:

photography/
├─ [album]/
│  ├─ index.astro
│  ├─ view.astro  <- only a single page

Optimized images in Svelte

Since it’s nearly impossible to directly use Astro components inside Svelte components, I needed to find a more reliable way to pass the Astro-optimized images to the Svelte album viewer component. This is also a challenge because I’m using Astro Icon to put icons on the site (from the amazing Tabler Icons set), but this provides an Astro component. Since I cannot use that within the Svelte component, but I still needed icons for closing or the back button, I also had to install unplugin-icons, potentially double-bundling some icons from the site. But back to the optimized images.

So, this is what I cannot do:

<svelte-component>
  <!-- Optimized images that would need to be wrapped -->
  <Image src="..." />
  <Image src="..." />
</svelte-component>

Luckily, Astro has a handy getImage() function to generate image data (and most importantly: URLs to the optimized images). So what happens instead is I import all the images in the view.astro page, call getImage() on them with the image settings and then pass the src attribute for each image as an array to the slideshow:

<Slideshow images={imageData} />

Then, the Svelte component can simply display each image using a plain <img> tag.

Directly linking to images

If you’ve opened the image at the top of this post and checked the URL bar, you’ve likely seen the solution: I’m using the URL hash to control which image to display. This brought with itself a number of other problems, like how many ways the page and the URL hash can be changed:

So it is a bit complicated, but carefully tying these changes together (and sometimes not letting the page change also set the hash, like on loading, to avoid always going back to view#1) can solve this issue without having to import another library.

Other, notable experience improvements

Besides the technical changes that should allow for significantly faster site builds, I also changed a few more things:

There are always things to improve and fix, but this one has been one of the largest issues lately, so hopefully these upgrades contribute to a more maintainable website.

Finally, here's another picture that I really liked from a recent session at Kungsträdgården in Stockholm.
Finally, here's another picture that I really liked from a recent session at Kungsträdgården in Stockholm.
Support