Hosting a Go Vanity Router with Cloudflare Pages

Make the 'go get' to your packages stand out

Introduction

Are you tired of having to expose your brilliant and professional Golang projects under your repository hosting service domain? Would you like to tell your coworkers to simply go get awesome.dev/amazing-project (please don't try to run that, no idea where it might bring you...).
In this short guide, I'll show you how I set up a simple router using Cloudflare Pages (and Functions).

go get-ing stuff

Go doesn't offer any main registry like cargo, maven or npm. It operates slightly differently, and we're used to go get or go install the code from the repository directly. Therefore, any developer that hosts their projects on sites like GitHub or GitLab will have their modules imports be something like:

go get github.com/name-surname/package-name

Of course, this is a valid and hassle-free way of making your work available to the world, but it ends up a bit boring, doesn't it?

The 'need' for a vanity router

A vanity router, in this case, would be a simple redirecting service that can route the go get request for a package to a custom URL. For example, with my router I translate all the requests coming to go.lorenzomilicia.dev/ to the corresponding GitHub repository under github.com/lorenzo-milicia/.

There is an actual real use case for the use of this rerouting mechanism, and it's the be independent of the git repository hosting service. You wouldn't want all of your users to have to change their import if you switched from GitHub to GitLab, right?

Behind the scenes

The <meta> tag

Implementing the rerouting logic is extremely simple. Whenever a get/install request is made, you're appending to a query parameter go-get=1 . If the HTML that is served contains the meta tag

<meta name="go-import"
  content="go.lorenzomilicia.dev/package git https://github.com/lorenzo-milicia/package">

then the server is telling the caller that the go.lorenzomilicia.dev/package is gettable, and specifies where it can be downloaded. Looking at the keys of the tag, we see the 'original' request and also the git repository to fetch the code.

The router

Building the router becomes easy! All that is needed is a server capable of returning us with an HTML page with the appropriate <head> tag, based on the path the custom domain gets called with.

Hosting the server

To serve this small service, I decided to use Cloudflare Pages and its Functions, which are very similar to Cloudflare's Workers. I figured that serverless would fit this use case quite well: whenever someone calls the URL (in this case whenever someone go gets one of my packages), the Function is executed, and the correct HTML gets served. This can be all done with a Cloudflare free subscription, as long as you mind the limitation on the number of requests that can be handled under this plan.

I'm not a Javascript/Typescript developer, so I'll spare you the simple code I wrote for my vanity router. I followed Cloudflare's documentation to build Functions in typescript.

After cloning my project, all you need is to use Clouflare's wrangler CLI tool to publish your Cloudflare Page. Again, I'll point you to the exhaustive documentation.

How to use the vanity router

The logic behind the router relies on three external Environment Variables:

  • CUSTOM_DOMAIN

  • REPOSITORY_URL

  • CUSTOM_ROUTINGS

The first two are simply the routing you want to take effect, composed as this:

<meta name="go-import"
  content="$[{CSTOM_DOMAIN}package git https://${REPOSITORY_URL}/package">

⚠ The protocol https:// is added in front of the repository URL passed as an environmental variable, so my implementation only works with the secure protocol!

The third variable should contain a comma-separated list of the custom mappings that the router should take into consideration when creating the repository URL.
For example, I want to keep all my library projects separate on GitHub, and I'll also most definitely write libraries with other languages. So I don't want to reserve a general libs prefix or directory name. But I would like the structure of my packages from the outside to keep a clean structure, so I want all my libraries to be gettable with the go.lorenzomilicia.dev/libs/. By settings CUSTOM_ROUTINGS="libs/,go-lib-", I'm telling the router that every request for a library /libs/ should go look for a GitHub repository that starts with go-lib.
Let's make it clearer with a table:

Custom Routing?InputOutput
go.lorenzomilicia.dev/packagegithub.com/lorenzo-milicia/package
✅ libs/ -> go-libgo.lorenzomilicia.dev/libs/best-evergithub.com/lorenzo-milicia/go-lib-best-ever

v2 and beyond

As you might know, Go has a peculiar way of handling major versions once you move to a 2.0 and above. Most notably, Go expects the module to be suffixed with a /v2 (or whatever major version the specific module is at). To correctly be able to route the requests to the repository that exposes a major version that requires this suffix, the path to the repository needs to be stripped from this.

The vanity router takes care of this, so the final outputs will be:

Major ⩾ 2?InputOutput
go.lorenzomilicia.dev/packagegithub.com/lorenzo-milicia/package
go.lorenzomilicia.dev/package/v2github.com/lorenzo-milicia/package
go.lorenzomilicia.dev/v4nitygithub.com/lorenzo-milicia/v4nity

Conclusions

Now you have all you need to host your personal Vanity Router for your Go projects, and all your friends and colleagues will be impressed by your professionalism, which under the hood will be mostly free of charge (excluding the domain you might want to route your requests from).
Check out the repository, and upload the files to your own Cloudflare Page to get started!

https://github.com/lorenzo-milicia/go-vanity-router
https://developers.cloudflare.com/pages/get-started/ https://developers.cloudflare.com/workers/wrangler/commands/#pages https://developers.cloudflare.com/pages/platform/functions/