I’m not sure why, but I received a bunch of inquiries about my photos page over the past week or two. Perhaps people were busy rebuilding their websites with Hugo over the holiday season. In any case, I figured it would be a good idea to write a post about how I created my photos page – that way I can have a resource to point to if I receive similar requests in the future.

First, I think it’s important to establish some context and go over what each image on my photos page actually is. On my blog, I have a number of post types – posts, pages, photos, notes, links, and more. Each post type has a specific purpose, and the “photos” post type is – as you can probably guess – for sharing photos. Here’s an example of a photo post. Each photo post has a featured image, which also happens to be the same image displayed in the body of the post. For my photos page, I simply pull the featured image from select photo posts and display them in a gallery.

Below is an example of the front matter for a photo post.

title: "50 Shades of Shinjuku Gyoen"
date: 2020-12-15T18:00:00+09:00
slug: 50-shades-of-shinjuku-gyoen
description: "It's strange how the trees in Shinjuku Gyoen still have leaves in mid-December, but the colors definitely make for a good photograph."
- Tokyo
camera: xpro3
lens: xf35mm
gallery_feature: true
featured_image: "2020/12/20201215_SHINJUKU-GYOEN-FOLIAGE.jpg"

If gallery_feature is set to true, the image referenced by featured_image is displayed on my photos page. I use the code below in the HTML template for my photos page. As a reminder, the code probably won’t work for you if you just copy and paste, but it’s pretty easy to understand what’s going on.

{{ define "main" }}

{{ $scratch := newScratch }}
{{ if hugo.Environment | eq "development" }}
{{ $scratch.Set "imgUrl" .Site.BaseURL }}
{{ else }}
{{ $scratch.Set "imgUrl" .Site.Params.cdnurl }}
{{ end }}

	<article class="page">
		<header class="post-header width-normal">
			<h1 class="post-title">{{ .Title }}</h1>
		<div class="post-content">
			{{ .Content }}
			<div class="photo-container">
				{{ range (where .Site.RegularPages "Type" "in" (slice "photo")) }}
				{{ if eq .Params.gallery_feature true }}
				<div class="photo-item">
					<a href="{{ .Permalink }}" data-no-instant>
							<img srcset="
							{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=320&ar=3:2&fit=crop&fm=pjpg&q=50&auto=format&blur=300&cs=strip 1024w,
							{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=320&ar=3:2&fit=crop&fm=pjpg&q=50&auto=format&blur=300&cs=strip 768w,
							{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=320&ar=3:2&fit=crop&fm=pjpg&q=50&auto=format&blur=300&cs=strip 640w,
							{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=320&ar=3:2&fit=crop&fm=pjpg&q=50&auto=format&blur=300&cs=strip 480w,
							{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=320&ar=3:2&fit=crop&fm=pjpg&q=50&auto=format&blur=300&cs=strip 320w"
						{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=1024&ar=3:2&fit=crop&auto=format&fm=pjpg&q=65 1024w,
						{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=768&ar=3:2&fit=crop&auto=format&fm=pjpg&q=65 768w,
						{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=640&ar=3:2&fit=crop&auto=format&fm=pjpg&q=65 640w,
						{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=480&ar=3:2&fit=crop&auto=format&fm=pjpg&q=65 480w,
						{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}?w=320&ar=3:2&fit=crop&auto=format&fm=pjpg&q=65 320w"
						sizes="(max-width: 480px) 50vw, (max-width: 960px) 33.33vw, (max-width: 1440px) 25vw, 20vw"
						src="{{ $scratch.Get "imgUrl" }}uploads/{{ .Params.featured_image }}"
						title="{{ .Title }}"
						alt="{{ .Title }}"
				{{ end }}
				{{ end }}

{{ end }}

First, I use the range function below to loop through my photo posts.

{{ range (where .Site.RegularPages "Type" "in" (slice "photo")) }}

After that, there’s an if statement to check for the gallery_feature parameter.

{{ if eq .Params.gallery_feature true }}

Finally, I use the value specified by {{ .Params.featured_image }} in the front matter to build a fully responsive <picture> element.

Oh, and there’s a few lines of CSS to make it look nice.

.photo-container {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
  margin: 3rem auto;
  position: relative;

.photo-container img {
  margin: 0;
  width: 100%;

.photo-item {
  width: 20%;

That’s it! If you’re looking to add a photo gallery to your Hugo site, I hope you found this tutorial helpful. If you have any questions about my photos page, or if you just want to share your awesome photos, feel free to send me an email or reach out to me on Twitter!