JAMStack URL Shortener with Netlify, 11ty and GitHub

Why...

I wanted a place where I can "bookmark" things I come across on the internet. Either for reading later, sharing or to keep a record of it for future reference.

For the sharing part I wanted to a way to use a shortened style link

Also, I want to own all this data. Along with not having to maintain a web application.

What if I could do all this with static site generation tool 11ty, and Netlify?

How...

Well turns out it's very possible, and much easier than you might think.

Broken down into the following parts

And the output of all this is my own URL shortner/bookmark "application" - https://roach.link

Netlfiy

For the Netlify part it's very straight forward. The provide a way to host a site, and also a mechanism for handling redirects. Setting up a site is pretty straight forward from their UI with a few customisable options. For the redirect part as long as you have a _redirects file in the directory you want to serve your site from they take care of the rest. The also allow you have use a netlify.toml file.

Eleventy (11ty)

For the 11ty part I went for a very similar set up to my personal site (this site) set up. Each link would be a markdown file inside a posts directory that looks like this

---
title: "Matthew Roach"
date: "2021-01-24"
permalink: "b/view/"
link: "https://matthewroach.me"
tags: ["personal", "blog"]
---

Personal website of Matthew Roach

The frontmatter data is where the majority of the needed data is housed. You may notice the permalink seems a little odd why is it b/view? The b part is the URL shortner unique ID, and the view part is used to build the output for each post to be roach.link/b/view allowing you to view the individual bookmark item (single view page still work in progress).

Hang on.. You said unique ID... That's going to be fun to maintain I hear you ask. Well it would be if you were trying to do it by hand. But I am not, I am using an npm package called bijective-link-shortener to increment them each time. Which is handled with GitHub as you'll see later.

Redirect file

Without the ability to do redirection a URL shortner isn't much use, and as previously mentioned Netlfiy allows you to do redirects if you provide either a _redirects or netlify.toml file in the root of the published site. And with 11ty this is made super simple to generate, as with 11ty you can configure it to output the input file to any output file (give or take), by setting the permalink to be the output.

Given all the bookmarks are all going to be individual files in a folder I set up a new collection in the .eleventy.js config file named allLinks and reversed the collection so the newest would come first.

With a file named _redirects.11ty.js in my src directory I can consume the allLinks collection and make 11ty render out the links in a format needed for Netlify. The _redirects.11ty.js looks as follows

class Redirects {
	data() {
		return {
			permalink: "netlify.toml",
		};
	}

	render(data) {
		return data.collections.allLinks
			.map(
				(l) =>
					`
[[redirects]]
	from = "${l.url.split("/")[1]}" // permalink is in format {shortLink}/view
	to = "${l.data.link}"`
			)
			.join("");
	}
}

module.exports = Redirects;

Broken down the file is looping over the allLinks collection, and setting up the Netlify redirect file in the format they require by using the permalink data (first part) and the link from each posts frontmatter data. As as the permalink of this file is set to netlify.toml it will create that file based on the render method.

The output file looks as follows

[[redirects]]
	from = "b"
	to = "https://matthewroach.me"

GitHub

GitHub is where I am hosted the source code and then have Netlify linked to the repo to build the site and publish on each commit to the main branch.

Issues

But GitHub is also the primary way I add new data to the site. Rather than having to create a new file and fill in all the frontmater data, and do a host of git commands I wanted a way I could actually use this "application" with minimal effort. Turns out GitHub issues is a great perfectly acceptable way to be the UI for adding new bookmarks... That is if you are willing to make a small adjustment.

So, GitHub issues provides all but one of the interface items I needed. This is how I mapped the GitHub issue UI to the frontmatter data

Title = title and link? That seems odd. Well turns out the issue UI doesn't really have a field or place I could enter the URL for the item I want to bookmark. So I went with the format of making the title as follows:

Matthew Roach Blog::https://matthewroach.me

The two colons has no meaning other than for when the GitHub action runs and splits the title using title.split('::') to parse it into two parts. The first part is the Title of the Site, and the second is then used in the link frontmatter data field.

Actions

GitHub actions is the glue that makes all the previous steps work together. Without actions the process of adding bookmarks would be very manual. Actions are a way for you to automate workflows from your GitHub repository. Based on an action you can configure an action to run and do "things". Those "things" are what makes this all possible.

As mentioned I am using GitHub issues as a UI to add new bookmarks. Broken down this works as follows

  1. Issue
    1. Add a new issue to my roach.link repository
    2. Once I am happy with the description and tags
    3. Mark the issue as closed
  2. GitHub action runs on issue closed event
    1. The action is triggered and starts its "steps"
    2. Action checks out code, sets up node and makes the issue object available on an environment variable
    3. Then a node command runs a script node new-item-from-issue.js 1. Within the script I have access to the issue object by doing JSON.parse(process.env.ISSUE_CONTEXT) 2. Script checks how many posts are currently in the posts folder, and using the package bijective-link-shortener gets the next short link value 3. Using the data from the issue object and the short link value from the previous step I use transformAndWriteToFile from the package json-to-frontmatter-markdown to create a new markdown file in the format noted above
    4. Add and commit is then run from within the action to add the newly created file to the repo
  3. Netlify is triggered due to a new commit on the main branch
    1. Netlify runs its build steps I have configured
  4. New version of site is live at https://roach.link
    1. Homepage is updated
    2. New redirect added - example: https://roach.link/b