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/<strong>sydcss-</strong>preso-gradient-circus/ would redirect to https://gilmoreorless.github.io/preso-gradient-circus<strong>/sydcss</strong>/. 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/<strong>sydcss-</strong>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<strong>/sydcss</strong>/.
  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.