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:
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:
- It’s difficult to pass Astro components to Svelte components. This is a big problem because I use Astro’s
<Image>
component to generate optimized images. - How to make it possible to directly link to a picture within an album like before?
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:
- The internal
page
state has to be initialized without the use of thewindow
global (because that’s only available in the browser), so it’s always1
during initialization, but then it’s set to the value specified in the URL hash when the component is mounted - Whenever the URL hash changes (handled using the
hashchange
event), thepage
state must also be updated - When pressing the left or right arrow keys, the
page
decreases or increases - When the user scrolls (on mobile), the
page
is updated - Whenever the
page
value is updated using the above two methods, the URL hash must also be updated
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:
- On mobile, you can swipe to scroll the images. It’s not (yet) as fluid as a native swiping experience, but perhaps limiting swipe-based scrolling to move only a single picture would help a lot.
- If you tap or click in the middle of the image, it zooms in to fill the screen, and you can pan around it. Pressing
Space
also works. - There is now a transition when moving between images, and they also fade in when loaded.
- There is now a back button on all pages, and the navigation menu has moved to the right.
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.