Adding Likes to Hugo site


Update (July 2026): the little backend I describe below grew into likeslikes.net, a free service anyone can use. If you want a like button on your blog you no longer need to build your own backend: register, verify your domain, and point your button at the public API. The likeslikes.net docs are the canonical, always up to date instructions. And if you don’t want to write any JavaScript at all, there is a drop-in widget that takes two lines of HTML. I have updated the code snippets in this post to use the public API.

Hey 👋, inspired by Brent Roose’s blog I decided to add a like button to all my posts in this blog. It’s a static site generated by Hugo since 2018.

It’s a nice little project that involved:

Integrating with Hugo

Let me start by saying that it was a bit difficult to decide where to add this feature. Hugo allows for multiple ways: overriding sections, shortcodes, …

First I started by implementing it as a shortcode, but when I was almost done I realised that this would require me to include in all the posts the {{< whatever_shortcode >}} shortcode, which I didn’t want to.

After that I tried to override the layouts/_default/single.html but when I was almost decided I checked the theme and there were some “partials” like the footer.

Tested the partials approach, but I didn’t like the result because I wanted my like button to be right after the post content. So I went back to the layouts/_default/single.html idea.

The process was very easy:

The frontend part

The frontend code is very simple. I separated HTML, CSS and JS in three different files.

layouts/partials/likes.html

<script type="text/javascript" src="/js/likes.js"></script>
<link rel="stylesheet" type="text/css" href="/css/likes.css">
<div class="likes">
	<div class="likes__button">
		<div class="likes__emoji">👍</div>
	</div>
</div>

static/css/likes.css

.likes {
    display: flex;
    justify-content: center;
    margin: 25px 0px;
}
.likes__button {
    display: flex;
    border-radius: 5px;
    border: 2px solid #ff8181;
    height: 50px;
    align-items: center;
    justify-content: center;
    padding: 5px;
    min-width: 50px;
}
.likes__button:hover {
    background-color: #ff8181;
    cursor: pointer;
}
.likes__emoji {
    font-size: 25px;
}

static/js/likes.js

const API = "https://likeslikes.net/api/v1/likes";

const sendLike = async () => {
  // Get current page url.
  const url = window.location.href;
  // Send like to the API.
  const result = await fetch(API, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ url: url }),
  });
  // Return number of likes.
  const { data } = await result.json();
  return data.likes;
};

// Hook the click handler when DOM is ready.
addEventListener("DOMContentLoaded", () => {
  const element = document.querySelector(".likes__button");
  if (element) {
    element.addEventListener("click", async () => {
      const likes = await sendLike();
      element.querySelector(".likes__emoji").innerHTML = `${likes} 👍`;
    }, { once: true } );
  }
});

Everything is ready in the frontend part! 🎉 What about the backend?

The likes API (backend)

I have to admit I got a little excited and overcomplicated (only slightly) the backend part. But it was for a reason, I plan on having this backend for other future projects.

The actual API is very basic. Only a couple of endpoints:

I will go in more depth regarding the implementation in an upcoming article! Stay tunned!

❤️ This backend is now likeslikes.net: a free service that hosts the likes backend for any blog, so you don’t need to build your own. The docs have everything you need to add it to your site.

Result

Here you have it. Smash the like button! 👍👍👍

PS: In the original version of this post the API was missing any checks, so you could like the same post many times. That’s fixed now: one like per visitor per URL (and no, it doesn’t store your IP, only a salted hash of it).

Frog easter egg