Perform a redirect with Jekyll and GitHub Pages
Wordpress is great but had too many bells and whistles for what I needed, not to mention the requirement of a MySQL server, so I recently decided to move to Jekyll and GH Pages. I'd previously had my own Apache instance where I could do all the .htaccess magic I liked, but how to perform redirects using Jekyll and GitHub pages?
GitHub pages (or gh-pages) provides basic free hosting for projects on GitHub. It's simple to use, just create a gh-pages branch and git push. It does exactly what it says on the tin and is a great free service offered by GitHub. One of the few drawbacks it has compared to other is the lack of configuration of the hosting environment, which included in this is the ability to perform transparent rewriting of URLs and redirections. I knew the limitations of GitHub Pages when I decided to move my website hosting from my own AWS EC2 instance but thought I'd try to find a solution to the lack of 301/302 redirection support. It turns out it's reasonably straightforward.
Redirection?
There are several types of redirection. By response header (301 & 302), 'meta' refresh and JavaScript. Since we don't have access to the server config we can't set response headers, but we can use meta tags and JavaScript to perform the redirect. It's not quite as good, but hey, we knew there were some tradeoffs right?
Meta Refresh
To perform a meta refresh we can simply include <meta http-equiv="refresh" content="0;url=/my/new/url" /> in the head of the page and the browser will automatically redirect to the new page. Simple.
JS Refresh
window.location can be used to perform a JavaScript redirect. So we can include a script tag in the head of the page to achieve the same result, something like <script>window.location = '/my/new/url'</script>.
I have always used the above mechanism to change the URL using JavaScript. While checking the window.location API I discovered window.location.replace(url). This does exactly the same thing but also replaces the URL in the history so users never see the original request if they use the back button. So our script could now be <script>window.location.replace('/my/new/url')</script>.
SEO
While this all works it isn't neccessarily very good for SEO. Since the exact SEO algorithms used aren't very public it's difficult to tell exactly how much worse it is compared to performing a 301/302 redirect, but regardlress of the exact value if SEO is important use a different hosting service and do it properly using headers.
Adding a canonical tag to the page could also help with SEO (it certainly wont hurt). To do this include the following in the head tag, <link rel="canonical" href="/my/new/url" />.
Putting it all together
So now we have something that looks like the following:
<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=/my/new/url" /><link rel="canonical" href="/my/new/url" /><script>window.location.replace("/my/new/url");</script></head><body>This page has been moved to <a href="/my/new/url">/my/new/url</a>.</body></body>
You'll notice I've also added some text in the body which will be visible to any users in case the browser fails to redirect for some reason.
Now since the reason we need to do this is because we're using Jekyll it would be nice to add this to a single location and include it when we need to redirect a page. Jekyll layouts to the rescue!
By creating a redirect.html inside of Jekyll's _layouts directory we can create a redirect layout to use for any page that should redirect somewhere. Since the URL wouldn't be the same each time we can replace the URL with {{ page.redirectTo }} to use a redirectTo property which can be configured per page. To use the redirect layout, simply include layout: redirect in the YAML front matter and use redirectTo: '/my/new/url' to configure the new URL.
So, to redirect /old/ to /new/, the file /old/index.html would have the following content.
---layout: redirectredirectTo: '/new/'---
and _layouts/redirect.html the following:
<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url={{ page.redirectTo }}" /><link rel="canonical" href="{{ page.redirectTo }}" /><script>window.location.replace("{{ page.redirectTo }}");</script></head><body>This page has been moved to <a href="{{ page.redirectTo }}">{{ page.redirectTo }}</a>.</body></body>
Bells and Whistles
It would be nice to use a full URL which includes the hostname rather than a relative URL. To prevent having to include the hostname every time we set the redirectTo property we can automatically add it instead. We need to do it conditionally though to still allow URLs to different domains.
{% if page.redirectTo contains '://' %}{% capture redirectUrl %}{{ page.redirectTo }}{% endcapture %}{% else %}{% capture redirectUrl %}{{ page.redirectTo | prepend: site.baseurl }}{% endcapture %}{% endif %}
This will set the variable redirectUrl which can be used in the template instead of page.redirectTo as we had before.
*Usually I'd put all of the above on one line otherwise extra whitespace is added to the page, but its on seperate lines here so it's easier to read.
TL;DR
So, to perform a meta redirect; a JavaScript redirect; & include a canonical tag we use the following files.
JEKYLL_ROOT/layouts/redirect.html------<!DOCTYPE html><html><head>{% if page.redirectTo contains '://' %} {% capture redirectUrl %}{{ page.redirectTo }}{% endcapture %} {% else %} {% capture redirectUrl %}{{ page.redirectTo | prepend: site.baseurl }}{% endcapture %} {% endif %}<meta charset="utf-8"><meta http-equiv="refresh" content="0;url={{ redirectUrl }}" /><link rel="canonical" href="{{ redirectUrl }}" /><script>window.location.replace("{{ redirectUrl }}");</script></head><body>This page has been moved to <a href="{{ redirectUrl }}">{{ redirectUrl }}</a>.</body></body>
and in any page we want to use the redirection template:
JEKYLL_ROOT/old/url/index.html---layout: redirectredirectTo: '/my/new/url'---