Making a Simple Self-Hosted Photo Gallery with 11ty
Tags: webdev, tutorial, frontend, 11ty
Published: 2024-06-05 15:11:39

For someone looking to make a personal space for their creative work on the web, the range of choices is so wide that it’s sometimes paralyzing! On the one hand, there are a ton of general-purpose website builders with gallery-like templates. On the other hand, there are specialized services for photographers and graphic designers focusing on hosting visual content.

But there’s a better way to do it—self-host! With a small upfront investment of your time, you can get a good-looking, unique, and fast website and publish it for free! And with this guide, it will be a breeze!

## First of all, why not use a website builder?

Many people are drawn to the simplicity website builders provide without realizing a bunch of limitations that come with it:
- **Lack of customization.** Most website builders have a set of predefined templates and components you can use. They do a decent job at helping you get a professional look but really fall short when it comes to making it personal. What you get in the end is often a clean but plain cookie-cutter website.
- **Branding restrictions.** Depending on the platform, branding options can also be pretty limited. Of course, you can always slap your logo on things and play with fonts, but what if your brand identity is more complex? What if you need a specific color scheme or a niche font? Things get even sadder when services push their own logo or branding on YOUR website.
- **Subscription costs.** A so-called “free plan” is often just a ramp to make you pay hefty fees. Can’t pay this month? Say goodbye to your hard work!
- **Vendor lock-in.** It’s rare to see a website builder have reasonable, convenient export options. No wonder why! They have no incentive to let their paid customers leave. And the longer you stick to one, the more time and effort it will take to migrate.

![That Sucks The Office GIF](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNjVudmFhZnE2Y3hkdGVhcHloNTB4ZTZiMnRrb3Fxemw5eHk4NWU4dyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/4cuyucPeVWbNS/giphy.gif)

It doesn’t sound so nice, right? Hopefully, now you’re convinced to give self-hosting a try! But before we jump straight into writing code, let’s decide where to store the images that will be displayed in our gallery.

## Picking a storage for images

Since we’re building a gallery, it must have some images. But where to store and how to serve them? The most obvious option is just commit them to the repository along with the source code. Don't do that, though! First of all, even though Git itself has no explicit limits on file or repository size, platforms do. GitHub, for example, will block any file that’s larger than 100 MB from uploading. And if the repository gets too heavy, you will be nicely asked to scale it down. Or else…

Git LFS is one way to address this. But even so, you still have to download gigabytes of data every time you clone a repository. Besides, there are not many independent Git LFS providers, and the storage costs can get pretty high quickly. You can approach file storage in many different ways, from S3 buckets to your own VPS with Nginx (who’s going to stop you anyway).

For this guide, we will use Macula.Link, a digital asset manager for artists, creators, and developers who value freedom and want to regain control of their content. Specifically, the features we’re going to use are called Data Sources and Universal Links. But more on that later.

> Learn more about Macula and sign up for a free, no-strings-attached plan [here](https://macula.link).

You don’t strictly need to use Macula to follow along with this tutorial, but I highly recommend you get a free account. It’s going to make things much easier!

## Preparing and hosting the images

A portfolio can’t exist without something to display. So step zero is to pick some images and have them at hand. You don’t need to process or compress them; we will do that later, right in Macula.

The first feature we’re going to use to make our lives easier is called [**Data Sources**](https://www.notion.so/f09c7c8bc44a4513b00e963e16f1abf7?pvs=21). In a nutshell, Data Sources act as endpoints for files and folders, allowing you to query details and metadata without loading the file itself. There will be a whole post on Data Sources soon, so make sure to give us a follow!

Log into Macula and create a new folder. Call it “portfolio” or anything that makes sense to you. Then open the folder and upload those images. Now let's do some magic! Click on the cogwheel button, activate the “Datasource” checkbox, set the license, and click Save.

![Directory publish settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yvvi6sk95q2ykbjuvgju.png)

With this single click, you have done a ton of work! Macula has automatically generated an optimized version of each image in the folder (the originals remain intact) along with a Universal Link to share it. You can see this by clicking on an image and going to the Transformations tab.

But wait, what’s the deal with those “Universal” Links? Well, they are called like that for a reason! Depending on how you make the request, each link can act as:

1. An **SEO-optimized web page** if you append `/` at the end of the URL.
2. A **direct link** **to serve the file** if you don’t append `/` at the end of the URL.
3. A **Data Source** with extensive details about the file if you add `.json` at the end of the URL.

> You can learn more about Universal Links and how they work in our [documentation](https://www.notion.so/Universal-Link-better-way-to-publish-share-ccba62079598451abbd961b9776e9ac1?pvs=21).

For now, we’re finished with the preparations. Let’s get to building the gallery!

Roll up your sleeves - let’s build!

![Let's build penguin GIF](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExejBoYzQwdW9sNzF0ZnA3aWlnOHZlc2RxaHJyY2NqMjRsOXh4c2g4YyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/qt73FYHjuXqAj241m8/giphy.gif)

We will use [Eleventy](https://www.11ty.dev/) as a static site generator of choice and [Netlify Drop](https://app.netlify.com/drop) for free hosting. Of course, this is not the only combination; feel free to use any tool and hosting you like. One of the cool things about Eleventy is that it supports multiple template languages. We’ll use Nunjucks for this tutorial. Don’t worry if you haven’t used it before. For this tutorial, basic HTML knowledge is enough!

Our gallery will have a single index page with a grid of thumbnails. The grid has to be responsive to look good on different screen sizes. Clicking on each thumbnail will lead the visitor to the preview page with all the information about the picture and quick links to share and use it, which looks like this:

![UniLink Preview example screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qh17jn9g5f87mucer9gk.png)

To help you get started even faster, here’s a template repository that contains everything we do further:

{% embed https://github.com/alxwnth/macula-11ty-gallery %}

### Scaffolding the layout

If you decided to follow along, buckle up! Create a directory for your portfolio, then create an `_includes` directory inside. We will put things like styles and layouts there. Then create another directory called `_layouts` and a `root.njk` file inside it with the following contents:

```html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>My personal gallery - {{ title }}</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta name="description" content="CHANGEME">
        <meta name="keywords" content="">
        <link rel="icon" type="image/x-icon" href="/favicon.ico" />
        <link rel="stylesheet" href="/style.css" />
    </head>
    <body>
        <main>
            {% block content %}
                {{ content | safe }}
            {% endblock content %}
        </main>
    </body>
</html>
```

This is a simple HTML boilerplate with two variables: `{{ title }}` to give each page a unique title and `{{ content }}` block is where the actual contents of the child pages will go. Notice that we add a `safe` [filter](https://www.11ty.dev/docs/filters/) here. It un-escapes everything we put on the page. Escaping is a technique often used in preventing [XSS vulnerabilities](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html). In simple words, escaping will render all HTML tags as plain text. So if we remove the `safe` filter, our contents will look like this:

![Screenshot of HTML page with tags](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6o5p6zvq2ys43i7lnwmo.png)

### Adding front matter

Next, create an `index.njk` and add the [front matter](https://www.11ty.dev/docs/data-frontmatter/):

```yaml
---
layout: layouts/root.njk
title: Homepage
---
```

Front matter contains the variables we need on the page, helping us specify the layout, title, and other data. By default, front matter follows YAML format but you can change it to JSON or even JavaScript Object.

Let’s add the links to our images to the front matter so we can iterate through them with a loop later. Note that you will need a *direct* link (without trailing slash), not a *preview* link!

Add a `photos:` key to the front matter with a few photos. The final result will look like this:

```yaml
---
layout: layouts/root.njk
title: Homepage
photos:
    - https://u.macula.link/ByD155QBR0yNgL94YRvsdw-7
    - https://u.macula.link/AEL6JsQoSk695cl805YWiQ-7
    - https://u.macula.link/vgJasPbpRNi57GTm_JGIgA-7
---
```

> These are real Universal Links, feel free to use them to play around and experiment!

### Looping through the images

Since we’re laz… I mean, efficient, let’s keep things DRY by using a loop instead of adding each individual image manually. An example of a Nunjucks loop iterating through an array of images looks like this:

```python
{% for photo in photos %}
    {{ photo }}
{% endfor %}
```

But simply rendering a bunch of URLs is not what we want. Let’s add some markup to start bringing the gallery to life:

```html
<div class="gallery">
    {% for photo in photos %}
        <div class="gallery-item">
            <a href="{{ photo }}/" target="_blank">
                <img src="{{ photo }}" width="800" height="600" alt="My image" />
            </a>
        </div>
    {% endfor %}
</div>
```

Notice that we use the same Universal Link both as `src` property of the image, and as link’s `href` property, but with a trailing slash.

### A touch of style

So far we just have a few images stacked on top of each other. This can be just fine if you’re into radical minimalism but let’s touch things up a little bit.

Inside the `_includes` directory, create a `style.css` file with the following contents:

```css
.gallery {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.gallery-item {
  flex: 0 0 calc(33.33% - 20px);
  margin: 10px;
}

.gallery-item img {
  width: 100%;
  height: auto;
  display: block;
}

@media (max-width: 768px) {
  .gallery-item {
    flex: 0 0 calc(50% - 20px);
  }
}
```

This is enough to have photos displayed in a flexible grid that adapts to the screen size: the larger the screen, the more images will be in a row. It’s a very simple style so feel free to expand and modify it according to your taste!

## Building everything

The final step before building is creating an `.eleventy.js` file where we will configure the [passthrough file copy](https://www.11ty.dev/docs/copy/):

```js
module.exports = function (eleventyConfig) {
  eleventyConfig.addPassthroughCopy({ "_includes/style.css": "style.css" });
};
```

What we do here is simply copy the `style.css` from the `_includes` directory in our working space to the root of the site.

Now just run Eleventy:

```bash
npx @11ty/eleventy
```

And you will see a new directory named `_site` that contains your built site, ready to be deployed anywhere.

## Deploy time!

Sign up for Netlify (or an alternative provider) and upload the `_site` folder. Once the upload is complete, the portfolio will come online!

If you have a domain name, you can use it instead of a subdomain provided by Netlify. To do this, go to **Domain Settings** and click **Add domain alias**. Enter the domain you want to assign to the site and follow the instructions to configure the DNS records.

And that’s it! Give yourself a pat on the back as you admire your creation going live on the World Wide Web. 🎉

![Excited Season 2 GIF by The Office](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExN2JsN295cHExdGdoZnRyaGZnMmY2YmxtNmpmdGZnMXFhYW45bDJneSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/IwAZ6dvvvaTtdI8SD5/giphy.gif)

## Further steps

This is pretty much it! Once the site is up and running, you have a ton of possibilities to make it even better. Here are some suggestions for you to get started:

- Make more versions of your images (e.g., smaller thumbnails) using [Macula Transformations](https://www.notion.so/Intro-to-image-transformations-3e96dc38215146409495009a9a65eb8d?pvs=21).
- Add a touch of your personal style to the website.
- Add pages for different albums, your info and contacts.
- Display image details by using [Macula Data Sources](https://www.notion.so/f09c7c8bc44a4513b00e963e16f1abf7?pvs=21).
- Have fun and experiment!

Once you’re satisfied with results, go show if off to the world, starting with this comment section!