Building a photo gallery site with Hugo

For some years I have been doing photography assignments, both private and commercial, and especially on Jazz, World Music and Classical concerts. Currently the “International Accordion Festival” takes place, which is a full month of concerts and workshops with accordion-related artists and groups, and it is my honor to accompany the festival photographically.

Of course the photos are posted on social media, but I was looking for a place to also publish the photos on a platform that is more suitable for browsing. Flickr and SmugMug came to mind, also fiddling around with Google Photos links, self-hosting Piwigo and Lychee, but none of these made me really happy.

My requirements were:

  • One page and (nice) URL per “Album”
  • Albums display thumbnails all of their photos
  • Albums have a title and a description (which is also in their OpenGraph metadata)
  • Albums are sorted newest first (date in the frontmatter)
  • Albums can have sub-albums
  • Photos in albums are sorted oldest first (date from EXIF)
  • Clicking on thumbnails opens a lightbox (PhotoSwipe)
  • Lightbox displays photo captions from EXIF/IPTC
  • No individual page per photo
  • No different layouts, types, sections, etc., everything is an album (although there need to be “legal” pages)
  • No dynamic or interactive features like comments, ratings, etc.
  • No pagination, no “endless scrolling” (albums are not that large)
  • Native browser lazy-loading, no “fancy” extra libraries
  • Very simple appearance, mobile-friendly, responsive

I came up with a static Hugo site with a custom Gallery theme built with Bootstrap 5. The site currently has well over 5.000 photos in over 200 albums, updating and publishing only takes a few seconds, and the site is served via an nginx Docker container.

(It is in German, but apart from the captions there are literally 7 translated strings, which should be quite self-explanatory)

Its source is on GitHub, and I am continuously improving it (to keep it simple, this is not a theme; it is not made to be part of a larger site but a small little simple gallery on its own):


Hi @nicokaiser , thanks for posting about the gallery site with Hugo. It looks great.

After seeing the repo, I was not able to figure out where and how you store the images.

Can you add some info regarding this ?

I’ll update the docs as soon as I can, currently I am busy shooting photos :wink: Although I added some info to this issue already:

BEP created an awesome image gallery using Dan Schlosser Pig JS library. You can check it out here. It is pretty badass.

See it in action.

It does not include the Opengraph tags but I am sure you can add it.

Your site that you built looks great and performs well. Maybe you can incorporate some of the functionality this solution brings to the table.

Thanks @devsr-gt! I am aware of bep/gallerydeluxe, however I wanted to have more control how metadata (album names, photo captions, etc.) is displayed. I might have a look at Pig (since it uses CSS transitions), but currently I am quite happy with GitHub - nk-o/flickr-justified-gallery: Flickr's justified images gallery

Thanks for your project which is awesome and fits my needs as yours.
I am testing it on a sample website but I am not able to display captions for photos (from exif or any other sources).
Would you mind provide me with some guidances in addition with those already in the documentation.

Glad you like it!
The captions and dates (for sorting) are read from the EXIF “ImageDescription” and “DateTimeOriginal” tags. Unfortunately Hugo cannot read IPTC tags, but you can set EXIF tags via exiftool or even convert IPTC tags to EXIF tags:

exiftool -ImageDescription="A very cute cat in front of a big house." -DateTimeOriginal="2023:04:09 16:11:00" DSC0001.JPG

In case the description is already in the IPTC tags (e.g. written by Photo Mechanic or other software) you can convert them:

exiftool "-ImageDescription<Caption-Abstract" DSC0001.JPG

Please let me know if this works for you!

1 Like

Thanks a lot,
It works when I set exif via exiftool.
But it only displays the -ImageDescription but not additional tags such as -DateTimeOriginal or -artist
Did I miss something to display all tags ?

In addition (If I may) do you have any guidance to host images separately from the site itself.
Meaning having a folder for each album in a hosting service (cloudinary, flickr or any else) and how to manage it with the hugo command when building the site

Can you try to remove the includeFields property in hugo.toml?

Regarding external image hosting, I do not really have an idea on how to achieve this. In this case, Hugo generates the lower resolution variants, so it is not really possible to host the variants on Flickr. You could theoretically host the „resources“ folder somewhere else, on a CDN, but I’m not really sure how to easily do this in a build script. (I did not investigate into this, as because of GDPR requirements I need/want to host everything on premise).

I don’t have any includeFields property in my config file.
Thanks for your swift answer

When testing, I add {{ $metadata }} somewhere in single.html (e.g. before the <a> tag) to see which metadata is available for each image.
I know that Hugo only extracts some EXIF tags (this is includeFields): Image Processing | Hugo Maybe you can try to set includeFields to ".*" so all metadata is extracted (which might produce some extra data, but might help to debug).

I made a theme out of this: GitHub - nicokaiser/hugo-theme-gallery

Here is an example site:

The documentation is not really complete yet, but you get the idea.


Thanks for sharing your work; much appreciated! I’m trying to create a gallery with currently some 20.000 images or around 60 GB of data in a nested hierarchy, and have run into a couple of challenges. The gallery has been converted from some older software (JAlbum) to a hugo based one, which may be part of the problem.

  1. It seems like some albums uri/slugs are converted to lower case, but not all. For some album I have created an file with a title (but not a slug) and for others that had no title in JAlbum there is no file – could that be the reason for some being lower cased and others not? The perhaps an empty file would help?

  2. I get the error message below when running hugo after it’s been through all files (a lengthy process). Any idea what could cause this – i.e. even if the access to exif is in a with statement, it seems to cause a hard error in line 12 in single.html, but it shouldn’t be trying to use exif on any non-image resources.

I do get a few warnings/messages prior to this (one says “WARN Unable to decode Exif metadata from image: xxx” and the other message is just “vnd.adobe.photoshop”). If I run exiftool on the file, it does produce a correct dump of the exif data.

Error: error building site: render: failed to render pages: render of "page" failed: "/tmp/hugo_cache_peb/modules/filecache/modules/pkg/mod/": execute of template failed: template: _default/single.html:12:27: executing "main" at <$original.Exif>: error calling Exif: this method is only available for image resources```

Thanks for any hints you could provide!

Hi @nicokaiser

This is using PhotoSwipe version 5, and I wonder why I need to add your repository as dependency which have so many other dependencies why I should not just directly add the photswipe with ES6 which Hugo uses already.

I do not like the photgallery of @bep because it have no support for mobile which makes it useless as user have to close the image and come back to click another image where with photoswipe one can easily swipe left right the images pinch etc.

is there an easier way to use your Photogallery in Hugo without the need to add your repo as dependency.


1 Like

This is false.

This is an unfortunate choice of words.

why I should not just directly add the photswipe with ES6 which Hugo uses already

I’m not sure if I understand this correctly, but Hugo does not alredy use PhotoSwipe by default. That’s why the hugo-gallery-theme bundles PhotoSwipe (to avoid having another dependency). The NPM dependencies from the theme are because of Tailwind CSS (tailwindcss, tailwindcss/typography, and PostCSS). I guess you already saw the hugo-gallery-example repository on how to use the theme.

I don’t think there is a more elegant/lightweight way to use Tailwind CSS in a theme, is there?

Like @jmooring said, it works pretty decent on mobile:

1 Like

Glad you like it! And I am really curious on how Hugo behaves for this amount of data!

From my understanding all URLs should be converted to lowercase unless you set disablePathToLower?

And, if there is no the folder will not be rendered as page (neither as album nor as list of albums).

So albums must be in a folder along with an (with at least title being in the frontmatter), and lists of albums must be in a folder along with an (again with title in the frontmatter). I’m not sure if there is a way to create pages with only folders of images with not Markdown files altogether…

This is strange. $original.Exif should only be read for “image” resources. Can you find one example image where this happens, or is this something that does not happen for one particular file? Are you using the latest version of Hugo?

I had problems when trying to use sort_by: Exif.Date for an album where not every photo has EXIF tags (unfortunately I did not figure out how to solve this yet). But sort_by does not relate to your problem, right?

Thanks for the reply! Well, hugo isn’t exactly an eagle for larger galleries… I’m running this on a pretty old machine (an Atom D525 at 1.8 GHz), so it takes more than 24 hours to build the gallery at the first run. Rebuilding is “fine” – it takes around 1 hour or so… :slight_smile:

I haven’t set the disablePathToLower, but it seems like hugo copies the files over to a non-lowercased directory if there aren’t any [] files. So by making sure all directories have proper [] files, paths/slugs are now all lowercased. So that was an easy fix running a small python script to convert existing meta data or just using default filenames to generate the [_] files.

As for the EXIF error, I placed one example of a file that fails here:

However, I tried to build a smaller gallery with only some of these failing files, and hugo then builds the directory correct – so the error message is apparently connected to some other file that it thinks should be an image file.

During the rendering process, it also occasionally spits out message like this:


so it looks liek some psd/nef/dng files in the file tree were causing the error. I did a couple of controlled tests, and indeed any of the psd/nef/dng files caused such an error, so I’ve removed all of these now and am re-running a build (which will take quite some time)… I’ll report back if this indeed removes the issue.

OK, so I managed to build the site – removing any NEF, DNG and PSD files fixed the errors.

I have now, however, another issue, which is perhaps more about the intended use:

My structure is set up so that some directories only contain other directories, whereas others hold a combination of images and (sub)directories – i.e.:


My root index.html file contains two “lists”; in the header-division, there is a class=mx-auto division that contains a class=w-full division that holds an ul list with all (in my case 12) subdirectories, as expected. However, the main class=mx-auto holds a div class=mb-12 that holds only 3 subdirectories – which appear to be the ones that hold images directly.

So, with reference to the example above, in the root directory the “hidden” list in the header part holds both DirA and DirB but the images shown in the main part holds only DirB (since it has an image directly underneath). For DirA, all 3 of DirAA, DirAB and DirAC are part of the hidden list but only DirAA and DirAC are shown in the main area.

Can I control this by assigning a featured_image that is in a sub-subdirectory, or do I need to include at least one image for each directory that holds other directories?

Also, if there are more than one image in a directory that holds subdirectories, they are not shown as part of that directory (i.e. in the example above, I3.jpg and I4.jpg will not be shown in the corresponding DirB, which contains only DirBA and other directories below.

Can this also be addressed somehow?

Again, thanks very much in advance – I think the layout of the gallery is really nice and better than some of the alternatives (for my use) :slight_smile: