Category Archives: Code samples

Redirecting GitHub Pages after renaming a repository

One of the golden rules I try to follow in web development is “don’t break URLs”. That is, even if pages get moved around, or a site is completely revamped, the old URLs should still redirect somewhere. It’s a terrible experience to bookmark a page or follow a link from another site, only to find that the page has completely vanished. Let’s try not to make the problem of link rot even worse.

The best way to redirect old pages is to get the web server to send a 301 Moved Permanently HTTP header. This has the benefit that search engines will see the 301 header and automatically update their caches to reference the new URL. Where this can fall down is when using static site hosting such as GitHub Pages or Surge.sh. By design, you’re not allowed to send any web server directives such as custom HTTP headers, so the 301 redirect option is out.

So what do you do when you want to move some URLs around when using GitHub Pages? Here are the options I found.

Moving a page within the same repository

Let’s say you have a repository with a gh-pages branch (or equivalent publishing branch) and you want to move/rename a page within that branch. For example, going from /oldurl.html to /newurl.html. There are two options for keeping the old URL alive as a redirect, as helpfully described by GitHub’s documentation.

Option 1: Running with Jekyll

If you’re using Jekyll for static site publishing, GitHub allows you to install the jekyll-redirect-from plugin. In your new page’s metadata, you specify which URLs it will redirect from. This will generate an HTML page at the old URL that simply contains a redirect <meta> element, pointing to the new URL. I haven’t used Jekyll much, so I can’t tell you much more than the documentation.

Option 2: Hyding from Jekyll

For everything else, you can manually create an HTML page that does the equivalent of the Jekyll plugin.

  1. First, move the file to the new location in git and commit that, so that the git history of the file remains intact: git mv oldurl.html newurl.html; git commit
  2. Create a new HTML file at the old location, containing the important meta redirect definition: <meta http-equiv="refresh" content="0; url=/newurl.html">
  3. Commit the new file, push both commits, and you now have an old-to-new redirect in place.

Waaaaaait a second, redirect meta tags ⁉️

Errr, yep. Even though the W3C recommends that redirect meta tags are not used, they’re pretty much the only option for this scenario. This is one of the big downsides of static site hosting. However, today’s browsers optimise those redirects pretty well. For accessibility purposes (and as a back-up if the browser fails to auto-redirect) it’s still best to include some text explaining the redirect, with a link to the new page. This is the boilerplate I’ve used previously:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf8">
    <meta http-equiv="refresh" content="0; url=https://newurl/">
    <link rel="canonical" href="https://newurl/">
    <title>This page has moved</title>
</head>
<body>
    <p>This page has moved. Redirecting you to <a href="https://newurl/">https://newurl/</a>&hellip;</p>
</body>
</html>

I’ve also got a canonical link in there as well. Even though search engines can usually understand the refresh meta instruction, there’s no guarantee that they’ll update their caches to point to the new URL. Adding a canonical link definition gives an extra hint to update to the new page.

Renaming an entire repository

GitHub allows you to rename a git repository and will automatically redirect references from the old name to the new one. This works for links to the web UI, API requests, git command line operations… but not GitHub Pages. Which means that if you have your documentation publicly published at https://your-username.github.io/old-repo-name/ and you rename the repository, none of the publicly cached references to your docs will redirect to the equivalent page at https://your-username.github.io/new-repo-name.

Note: If you’re using a custom domain name (e.g. my-superb-docs.com), this should be handled without your intervention. The public URL doesn’t change at all, just the source location of the files. For everything else, however, there’s a workaround available.

GitHub allows you to create public websites in two ways. One has already been featured above, which is having a specific part of your repo (either a branch or directory) hooked up to the publishing feature. This gives you a URL of https://your-username.github.io/repo-name/ – where the repo name will always be a subdirectory of the top-level domain.

The other way is using a repository called your-username.github.io. Anything that’s pushed to the master branch in this repo will be visible at (surprise, surprise) https://your-username.github.io/, including directories. So you can publish a subdirectory to a top-level repo, or publish to the root of a different repo, and they’ll both be visible as subdirectories of the public domain.

But what about name conflicts?

  • Pushing a directory called some-project to the master branch of the your-username.github.io repo will give you a URL of https://your-username.github.io/some-project/
  • Pushing the root files to the gh-pages branch of the some-project repository will also give you a URL of https://your-username.github.io/some-project/

Through some quick testing, I found that the gh-pages branch (or equivalent) of the project repository will always override the subdirectory of the parent repo (your-username.github.io). This is what can use to our advantage, by treating the parent repo as fallback content when renaming the project repo.

Step by step, mile by mile

To give a solid example, this is the scenario I encountered recently. I had a presentation slide deck that I’d done for a local meetup (SydCSS), with the repo name sydcss-preso-gradient-circus. Then I gave that presentation for a mini-conference (Decompress) – I wanted to publish both sets of slides (as the content differed) at a more generic URL, while still having the old URL redirect to the new one. So https://gilmoreorless.github.io/sydcss-preso-gradient-circus/ would redirect to https://gilmoreorless.github.io/preso-gradient-circus/sydcss/. These are the steps I took:

  1. Created sydcss-preso-gradient-circus/index.html within the gilmoreorless.github.io repo. It contained the meta redirect example shown earlier, pointing to the new URL of the SydCSS version of the presentation.
  2. Copied all the files in the gh-pages branch of the sydcss-preso-gradient-circus repo to a sydcss subdirectory within the same repo. This meant the root page / and the subdirectory /sydcss/ both had the same content.
  3. Published the Decompress version of the presentation to the decompress subdirectory of the same repo.
  4. Renamed the repo in the GitHub web interface, from sydcss-preso-gradient-circus to preso-gradient-circus. At this point, any requests to https://gilmoreorless.github.io/sydcss-preso-gradient-circus/ would hit the fallback page created in step 1. That would then redirect them to the new URL of https://gilmoreorless.github.io/preso-gradient-circus/sydcss/.
  5. With the redirect in place, I changed the index.html of the preso-gradient-circus repo to just be a simple list of links to the different versions of the presentation.

In summary, the situation right now is:

It seems like a lot of effort when it’s written down like that, but it only took a few minutes to do the process. It took me far longer to write this post than to set up the redirects (including the testing of fallback content). I think it was worth it; even though it was just a slide deck, I care about keeping URLs working wherever possible.

Also, this post is mainly serving as documentation for Future Me in case I ever need to do it again. Anyone else getting a benefit from it is merely a bonus.

Strip analytics URL parameters before bookmarking

Have you ever found yourself bookmarking a website URL that contains a heap of tracking parameters in the URL? Generally they’re Google Analytics campaign referral data (e.g. http://example.com/?utm_source=SomeThing&utm_medium=email).

I use browser bookmarks a lot, mostly as a collection of references around particular topics. The primary source for most of my web development-related bookmarks is a variety of regular email newsletters. My current list of subscriptions is:

What many of these have in common is the addition of Google Analytics tracking parameters to the URL. This is great for the authors of the content, as they have the option to see where spikes in traffic come from. When I’m saving a link for later, though, I don’t want to keep any of the extra URL cruft. It would also be giving false tracking results if I opened a link with that URL data a year or two later.

So I wrote a quick snippet to strip any Google Analytics tracking parameters from a URL. I can run this just before bookmarking the link:

/**
 * Strip out Google Analytics query parameters from the current URL.
 * Makes bookmarking canonical links easier.
 */
(function () {
    var curSearch = location.search;
    if (!curSearch) {
        return;
    }
    var curParams = curSearch.slice(1).split('&');
    console.log('Stripping query parameters:', curParams);
    var newParams = curParams.filter(function (param) {
        return param.substr(0, 4) !== 'utm_';
    });
    if (newParams.length === curParams.length) {
        return;
    }
    var newSearch = newParams.join('&');
    if (newSearch) {
        newSearch = '?' + newSearch;
    }
    var newUrl = location.href.replace(curSearch, newSearch);
    history.replaceState({}, document.title, newUrl);
})();

I have saved this snippet in my browser’s devtools. See github.com/bgrins/devtools-snippets for more details of how these work.

However, some people prefer to use bookmarklets, without fiddling around in browser devtools. I’ve converted the above snippet into a bookmarklet as well. If you’re using a desktop browser, you can drag the following link to your bookmarks area to save it:

Strip GA URL params

Animated demonstration of using the bookmarklet link