DEV Community

Evan Salter
Evan Salter

Posted on • Updated on • Originally published at evansalter.com

GitHub Pages and Single-Page Apps

This post originally appeared on my personal blog.

Single-Page Apps (SPAs) are all the rage right now. They make it extremely easy to build feature-rich, highly performant web applications. So you go ahead and build yourself a SPA, but one question remains. Where do you host it?

This article is separated into two parts. First, an outline of the different SPA hosting methods I have used, along with their pros and cons. Second, my experience with hosting an SPA on GitHub Pages, including any trouble I have had. I hope to help the readers make an informed decision regarding where to host their app, and if they do choose to use any of the methods I talk about, learn from my mistakes.

If you wish to focus on GitHub Pages, and skip the other methods, you can jump to section 4: GitHub Pages.

1. Google App Engine

I didn't really get into web development until I started my current job. Yes, I knew some HTML and CSS, and I had taken a university web dev class. However, I wasn't all that interested until I started using those skills in the workplace.

At my job, all of our core applications are built using App Engine. We use the Python version, with Jinja2 as our templating language, and Knockout.js for frontend code. After a while I began to feel very comfortable developing in App Engine. It was familiar, and I had gotten to the point where I could throw together a new application relatively quickly and easily.

Now, back to SPAs. In the summer of 2016, I started working on Roll Up Tracker. Roll Up Tracker is an Angular2 web-app that allows you to track your wins and losses during the Roll Up The Rim To Win season at Tim Hortons. Naturally, when I started the project I decided to host it on App Engine. It might seem a bit odd to have to bootstrap an entire App Engine project just to serve some HTML and Javascript. However, I justify it by the fact that the backend was written in Python, using Google Cloud Datastore as the database. It made sense to keep the frontend hosted alongside that.

The implementation was pretty straightforward.

  1. Add a couple rules for the frontend files, and a backend catch-all to your app.yaml
  2. Create a wildcard route to a handler that just serves index.html
  3. Create your API routes above your wildcard so they get matched first
# app.yaml
- url: /assets/(.*)
  static_files: dist/assets/\1
  upload: dist/assets/.*

- url: /(.*\.(js|map))$
  static_files: dist/\1
  upload: dist/(.*\.(js|map))

- url: .*
  script: main.app
  secure: always
Enter fullscreen mode Exit fullscreen mode
class MainHandler(BaseHandler):

    def get(self, *args, **kwargs):
        context = {}
        self.response.write(template.render(os.path.join(TEMPLATE_DIR, 'index.html'), context))
Enter fullscreen mode Exit fullscreen mode

That's about it! Moving on to method number two...

2. Google App Engine Flex

Oooooh "App Engine Flex"! Sounds new and shiny and perfect and awesome. It was, at first.

I had built a very small SPA using Vue.js called Scrobblin' With Friends as an attempt to bring Last.fm back into my life. It's a simple app that allows you to enter your Last.fm username, and it shows you what all your friends are listening to. It's a tiny app, with absolutely zero backend. All it does is take in a username, hit an API every ~10 seconds, and display some data. It definitely didn't make sense to bootstrap an App Engine project.

Then I discovered Flex, and found out they have a Node.js environment. It was extremely easy to set up:

  1. app.yaml with 2 lines: runtime: nodejs and env: flex
  2. package.json with a start script
  3. A simple express server
// server.js
var express = require('express');
var app = express();

const PORT = process.env.PORT || 8080;

app.get('/static/*', function(request, response){
     console.log('static file request : ' + request.params[0]);
     response.sendFile( __dirname + '/static/' + request.params[0]);
});

app.get('*', function(request, response) {
    response.sendFile(__dirname + '/index.html');
});

app.listen(PORT);
Enter fullscreen mode Exit fullscreen mode

I finished the setup very quickly, deployed it, and life was good. That is, until I got my bill for the month.

One of the big differences you notice between the Standard App Engine environment, and the Flex environment, is that your project never completely spins down. On Standard, if nobody is hitting your site, you don't pay. Even if you're getting a little bit of traffic, they're quite generous with the free tier. Unbeknownst to me, "free tier" isn't a thing on Flex. I racked up about $30 in a couple days before I noticed.

I could see Flex being a viable option if you have a project you were willing to pay big bucks to host. This was not one of those projects. So back to App Engine Standard I went.

3. now

If you haven't heard of now, go check it out now (heh). It is seriously cool. After installing it on your computer, just type now on your command line, and next thing you know, your application is on the web.

The only requirement is that you have a package.json with a start script. now takes care of the rest.

As cool as the deployment process is, there were some things I didn't like about it.

  1. The free tier gives you these janky URLs for your project, for example: coolproject-glkqdjsslm.now.sh. I usually don't mind having strange URLs when I'm not paying for hosting, but the issue is that every time you deploy, you get a new URL. You basically can't host anything that other people are going to use for free
  2. Their cheapest paid tier, which gets you 1000 deploys/month (instead of 20), private codebases, and more is $14.99/mo. Much more than I was willing to pay for silly side projects.

4. GitHub Pages

Finally. You sat through the excruciating minutia of my SPA hosting experiences. We have finally arrived at the point of the article, which is to outline my experiences hosting an SPA on GitHub Pages.

It seems like the logical place to serve a SPA from, right? You've got free hosting. Deploys are a breeze. Free SSL. Plus your code is probably going to be kept on GitHub anyway!

I had recently decided that it was time to replace my personal website, which was previously a free Wordpress site that I put zero effort into (and I eventually password protected it because I was embarrassed by it). My new site, http://evansalter.com, is written in Vue.js with no backend, so I could really choose any provider I wanted. App Engine (both Standard and Flex) were overkill and/or too expensive, now was also unfeasible because of the reasons I stated above. So here we are. GitHub Pages is the chosen one.

It is not without it's faults. Of course, there are a few quirks here and there that you might need to work around, but I haven't come across any blockers, and I'm sure you wouldn't either. That said, if GitHub Pages were a paid service, some of the problems I encountered would have made me throw in the towel and switch to something else. Since it's free, I was willing to put in the time to work around the issues.

I'd like to walk through how the whole thing works, including any "gotchas" that I encountered along the way.

Part 1: What is GitHub Pages?

First off, go here. It has all the basics of Pages, and if you want more detailed documentation you can click "Pages Help" in the top-right corner.

There are two types of Pages. The first is an Organization/Personal page. This lives in a repo called <YOUR_USERNAME>.github.io, has a URL that matches the repo name, and is meant to be a general landing page for a person or organization (duh). The code for the site lives in this project.

The other kind, a Project page, most commonly lives in the gh-pages branch, or in a /docs folder on master. Then, you access the page from <YOUR_USERNAME>.github.io/<PROJECT_NAME>. It allows you to keep all the code in one place. Pretty handy, I think.

For the purposes of this post, I will be talking about Personal pages.

Part 2: What should my repo look like?

This was the first problem I came across. A personal site is served from the master branch. That meant I couldn't just create a project using the Vue CLI, commit everything to master, and expect it to work.

After doing some research online, I settled on a plan. I created a sources branch that houses the source files. Then, on my computer, I built the project, initialized the generated dist directory as a git repo, set it to track master, then committed and pushed. It worked pretty well, but it was largely manual. Plus if I ever re-cloned the project or used another computer, I'd have to set up the dist repo again. Annoying.

Part 3: Automating Deployments

I was not about to settle for the process laid out above, so I figured I could devise a script that could do that for me, then just set up Travis CI to run it. There are quite a few scripts floating around the internet that do exactly that. I played with one for a while, tweaking it to work for my project, when I found out that Travis actually has a GitHub Pages deployment provider. A couple commits later, I ended up with a pretty simple .travis.yml that took care of everything for me.

language: node_js
node_js:
  - "node"
script:
  - yarn run lint
  - yarn run build
deploy:
  provider: pages
  skip_cleanup: true
  github_token: $GITHUB_TOKEN
  local_dir: dist
  target_branch: master
  on:
    branch: sources
Enter fullscreen mode Exit fullscreen mode

It basically just runs my lint and build, then if the branch is sources, force-push the dist directory to master. For more info on this, check out the Travis CI docs.

Part 4: Routing

It was very smooth sailing for a while. I was developing locally very quickly using the webpack dev server, complete with hot module reloading. When I wanted to deploy, I just pushed up my changes and in a minute or so they were live. I had no problems setting up the Vue router at all.

As it turns out, when I was using the live site I was always starting from the root of the site. I never tried to go directly to /projects or any other page. That's when I hit a snag. Trying to go directly to a route in my app resulted in a 404 page.

I definitely wasn't shocked when I discovered this. It makes total sense, since GitHub doesn't know about the Vue app's routes. All it knows is there is an index.html file in the repo that it can route to.

I attempted two different solutions to this problem. Depending on your requirements, one may be more appropriate than the other.

Attempt 1: Hacking the 404 page.

I found another person online that was trying to solve the same problem as me. You can see his blog post here. I'll admit it's quite a janky solution to the problem but it was also simple to implement. Until GitHub comes up with a better solution, this is a pretty decent workaround.

This solution is based on the fact that you can use a custom 404 page on GH Pages. All you have to do is add a 404.html page to your repo. Following the link above, I created 404.html with the following lines in <head>:

<script>
  sessionStorage.redirect = location.href;
</script>
<meta http-equiv="refresh" content="0;URL='/'"></meta>
Enter fullscreen mode Exit fullscreen mode

Basically all this does is save the requested URL in your browser's storage, and boot you back to the root of the site. Obviously, that's not enough. If someone click a link to this blog post, I wouldn't want them being sent to the homepage. The next step was just as simple. I basically added another script to index.html to check for that saved URL, and redirect to it if it exists.

<script>
  (function(){
    var redirect = sessionStorage.redirect;
    delete sessionStorage.redirect;
    if (redirect && redirect != location.href) {
      history.replaceState(null, null, redirect);
    }
  })();
</script>
Enter fullscreen mode Exit fullscreen mode

This worked surprisingly well. However, once I decided to implement Open Graph and Twitter meta tags in my blog posts, it fell apart due to the scrapers not following the redirects. However, if you have a simple site that doesn't require meta tags, then this just might work for you.

Attempt 2: Pre-rendering a Static Site

There were two issues with the above router hack in terms of serving meta tags:

  1. When Facebook, Slack, Twitter, or any other site with rich media cards would scan my links, they would land on the 404 page as expected. However, they would not get redirected to the proper page by the method I shared above. This is because GitHub Pages returned a 404 status code for the 404 page, which told the scrapers to give up.
  2. Even if I didn't use the 404 page hack, my meta tags were generated on the fly in my Vue app. Since the scraper downloads the page without executing any javascript, it would not find the meta tags.

So, the two obvious solutions are server-side rendering or pre-rending. I didn't want to go down the path of server-side rendering, since I don't want to host a server for this website. So, pre-rendering it is!

Since I'm using Webpack, I thought it made sense to add a webpack plugin to generate static HTML from my site. I found a couple prebuilt plugins to do this, which both leveraged PhantomJS to render the site. For some reason, I could not get it to render any actual content in my site. I wasted a ton of time trying to get it working, with no success.

Frustrated with that, I decided to write my own plugin. Thus, webpack-static-site-generator was born (great name, I know). With that plugin in place, every time I built my site for production I would get static HTML files for all of my pages. I could still use the Webpack dev server when developing locally without building, which was a huge plus.

So, if you have any trouble hosting your SPA on any hosting provider, you might want to check out pre-rendering.

Part 5: Custom Domain

If you've made it this far, you should be done with the "gotchas" of hosting a single-page app on GH Pages. Setting up a custom domain was very easy. There were just a couple steps:

  1. Create a file called CNAME in your repo, which contains a single line with the domain you want to use (i.e. www.evansalter.com). Make sure this file get commited to your master branch when the build runs.
  2. Set up a CNAME record with your domain registrar that points to your github.io address (i.e. esalter-va.github.io)

There are some slightly different instructions on GitHub's support pages if you want to use an apex domain (no www), but it shouldn't be too much work.

There was one slight problem I discovered. There is NO SSL support when you're using a custom domain with GH Pages. I was a tad annoyed at this, but seeing as my site has no real reason to be on HTTPS, I got over it.

Final Thoughts

I very excited about using GitHub Pages to host a single-page app when I first started this site. However, it wasn't quite as magical as it seemed. I faced many problems along the way. Some of them had simple solutions, while others were a little more complicated. However, I'm still happy with how it worked out in the end. The simplicity of Pages is what makes it great, and I would recommend that everybody at least considers it for their next project.

Oldest comments (30)

Collapse
 
tbodt profile image
tbodt

netlify is cool

The free tier gives you everything GitHub Pages gives you, plus HTTPS with LetsEncrypt, URL rewrites/redirects, and the ability to use any static site build system (not just Jekyll). Plus if your site is for an open-source project, you get the $50 a month plan for free. I have no plans to ever use GitHub Pages ever again.

Collapse
 
ben profile image
Ben Halpern

Thanks for the heads up, looks great.

Collapse
 
_evansalter profile image
Evan Salter

Wow that looks pretty cool. I'll definitely be looking into that more! Maybe I'll move my site to that, or keep it in mind for a future project. Thanks!

Collapse
 
nrobinson2000 profile image
Nathan Robinson

Thanks for recommending netlify. I've now moved my portfolio and the po-util website there now. Digging the easy setup and free SSL.

Collapse
 
empty2k12 profile image
Gero

You can use CloudFlare as a reverse proxy with GH-Pages to add SSL encryption and other nice extras.

Collapse
 
hugmanrique profile image
Hugo Manrique

I did something similar for a Jekyll website I was building, but I was using the dev branch to commit the root project, and then, my npm run build or yarn run build would automatically cd to the build folder, check the master branch and commit for me.

For the HTTPS problem you're having: you can either use CloudFlare (which will boost your website performance (http2, caching, 100s of PoPs)) or create Let's Encrypt certs which you need to provide your DNS manager (you may not be able to do this).

Collapse
 
_evansalter profile image
Evan Salter

Yeah I thought about using CloudFlare but only the traffic between the user and CloudFlare is encrypted. Traffic between GitHub and CloudFlare will be plain text. There's probably still an advantage, however I don't like the idea of making the user think their traffic is encrypted end-to-end when it's actually only encrypted half way. If my site transferred any sensitive information, I definitely would have searched for another solution.

Though it might be worth it to look into CloudFlare for the other benefits it can provide!

Collapse
 
sergiodxa profile image
Sergio Daniel Xalambrí

With now you can alias your deployments to a better URL, in the free tier you can alias to now.sh sub domains (eg. coolproject.now.sh), if you paid you can use a custom domain but I think for OSS projects (free tier) a now.sh sub domain it's OK

Collapse
 
_evansalter profile image
Evan Salter

Definitely good to know about the subdomain aliases. I like to use now for prototypes that I can easily share with others, so that will come in handy!

For my personal site, I wanted to use evansalter.com for the domain, so that wouldn't have worked for me unless I wanted to pay. I definitely haven't ruled out now for other types of projects though, as I do like what the service offers. Thanks for the tip!

Collapse
 
orhanveli profile image
orhan

Great article thanks a lot. I think hexo or metalsmith are worth to try for static pages generation.

Collapse
 
gildaswise profile image
Gildásio Filho

As for static page generators, I recently did my personal page too and found Hugo on the way. It's pretty easy to get it running, try it later!

Also, it's always nice to know about other ways to host SPAs and thanks for writing!

Collapse
 
jaydp17 profile image
Jaydeep Solanki

I faced similar issues when I was planning to host my SPA.
Same as you I too did a lot of research and finally settled on Firebase Hosting as it has support for SPA and free SSL for custom domains, plus it's free if your site traffic is less than 10G/month

Collapse
 
afirstenberg profile image
Allen Firstenberg

I strongly concur with Firebase Hosting. Several advantages in my mind:

  • As noted - SSL is provided. Strongly encouraged, even.
  • Easy integration with the Firebase DB, which was designed for SPAs to do a lot of things without requiring a hosted back-end middleware.
  • Easy integration with Google Cloud Functions, which provide some of that middleware support with node.js if you need it. Only bills you for the time actually used (and it scales to zero, unlike the newer App Engine offering).
Collapse
 
_evansalter profile image
Evan Salter

Yeah I have heard great things about Firebase Hosting. I kind of got hooked on the idea of having zero costs associated with this project, just to see how easy it would be to do. Though the costs through Firebase would have been negligible. I'll keep it in mind for my next project!

Collapse
 
kalinchernev profile image
Kalin Chernev

Hi Evan, really thanks for this post. I will try to steal your travis script :) :P

I also moved recently from Medium to github pages (github.com/kalinchernev/kalinchern...) and I also bumped my head against the wall with the master branch trick. My deployment scripts are also just fine for the moment (github.com/kalinchernev/kalinchern...) but I'll try to make use of your example :D

By the way, on my similar journey like yours, I met several other ideas and concepts:

Basically, we're trendy! It's the cool way to make personal sites and blogs nowadays :))

Collapse
 
_evansalter profile image
Evan Salter

Yeah I found it kind of annoying that you can't serve a user or org site from a specific directory on master like you can with the docs folder on the project sites. Though once I got it set up with Travis, I did prefer that way since it keeps the built files separate from the source code.

I didn't know about that gh-pages node module, it looks pretty handy! Thanks for the links, I'll definitely check them out.

Collapse
 
mindlace profile image
Ethan Fremen

Glitch.com is pretty cool too. It's a little bit challenging for me to wrap my already tool-soaked head around their development model, but they clearly let you build some cool things.

Collapse
 
jgb profile image
Jean Gérard Bousiquot

Netlify is pretty good.

I've been using Github Pages for a while but then I stumbled upon Netlify and I never looked backed since. They offer a lot more features than Github Pages in their free plan.

Collapse
 
guitarino profile image
Kirill Shestakov

A great article! I just published my personal website via Github pages. I also created a Build branch where I have a build system for my website, and then I manually publish the output to the master branch. Thanks for the advice on automation, I'll definitely take a look at improving the process.

By the way, I'm not using any framework, and I built a simple router that queries the requested page and diffs it with previous page. I'm also using Web Components to help with the process, it allows me to extend an Anchor HTML element, which makes it easier to override links and to make sure the site works even without JavaScript.

Collapse
 
jswhisperer profile image
Greg, The JavaScript Whisperer

Rollup the rim to win, the epitome of Canadiana, Thanks for the article too!

Collapse
 
reeder29 profile image
Doug Reeder

Surge.sh is like now, but you can pick the subdomain (so you can keep using the same one). Also, by copying your index.html to 200.html, routing is handled.

Collapse
 
Sloan, the sloth mascot
Comment deleted

Some comments may only be visible to logged-in visitors. Sign in to view all comments.