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:
- Learning how to extend Hugo (and the template I am using) in the least disruptive way.
- Do some vanilla JS.
- Craft some very basic API that I can use to kickstart
api.aaron.com.es(separate article is in the oven 🍞!).
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:
- Copy-pasted theme’s original layout with a command like this:
cp themes/manis/layouts/_default/single.html layouts/_default/single.html. - Edited the file to include a new partial in the correct place:
{{ partial "likes" }}. - Created the partial at
layouts/partials/likes.html.
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:
GET /api/v1/likes?url={the-url}: Will return a JSON containing the number of likes.POST /api/v1/likes(with{"url": "{the-url}"}as JSON body): Will increase the counter of likes for the given URL by one and also return the number of likes.
I will go in more depth regarding the implementation in an upcoming article! Stay tunned!
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).
Send a comment