There are quite a few posts out there that try to explain how to set up S3 and CloudFront to serve up your single page Angular2 app.  However, they are incomplete, lead you into more configuration than is necessary and/or don’t do what I think is best practice.

This HOWTO will take you step-by-step through the following:

  • Domain registration via Route53
  • FREE SSL certificate creation via Certificate Manager (ACM)
  • Naked/apex domain support
  • Multi-domain redirection without servers
  • Gzip’ing and uploading your Angular2 app to S3 WITHOUT the need for static website configuration
  • Serving your app out of CloudFront CDN with support for: SSL, root apex, SSL upgrading, Angular2 routing and 1st load sub-directory URLs, far future content expiration (for high cache hit ratio), HTTP/2, and multiple domains

In the end you will have a lightning fast SECURE page, with fortune 500 reliability and durability – all for cheaper than you will believe.

Step 1: Register your domain(s) and create SSL cert via ACM

I highly recommend using AWS Route53 to register your domains.  It’s $10/year for .com’s with no bullshit price changes or coupon hunting.  Super easy to use and gives you lots of easy integration with other AWS services like CloudFront, EC2, ELB, S3 etc.

For this example we pretend I registered rynop.org and rynop.com

Next go to the  Amazon Certificate Manager (ACM) console page and request a certificate.  Put in your root apex(es) (aka naked domain) and all your subdomains.  ACM SSL certs are FREE and they auto-renew.  It’s similar to letsencrypt but zero overhead.   Ex:

rynop.org, *.rynop.org, rynop.com, *.rynop.com

You will get an email to your domain admin contact email addresses to approve.

Step 2: Create S3 bucket and upload hello world HTML page

Create an s3 bucket (Ex: http://www.rynop.com).  Create a file named index.html with the following contents.  When you upload the file make sure to give it public read permissions.

<!doctype html><html lang=en><head><meta charset=utf-8><title>rynop.com</title></head><body>Hello World</body></html>

Note: you will notice I did NOT setup static website hosting on my S3 bucket.  It is not needed as CloudFront will handle it all for you.

Step 3: Create and set up CloudFront distribution

Create a new CloudFront distribution, specify the “web” delivery method.

Use these settings (ones I do not mention, leave the default value):

  • Origin Domain Name: (this should auto-complete to your s3 bucket) ex: http://www.rynop.com.s3.amazonaws.com
  • Viewer Protocol Policy: Redirect HTTP to HTTPS
  • Allowed HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE (we want to do CORS)
  • Forward Headers: BothAccess-Control-* and Origin
  • Query String Forwarding and Caching: Don’t choose None if you use query string in your Angular app
  • Compress Objects Automatically: Yes (It won’t re-gzip content)
  • Alternate Domain Names: http://www.rynop.com. I omitted the root apex domains as we are going to setup another CloudFront distro in to redirect those to http://www.rynop.com. Don’t do naked domains over HTTPS. Google this if you care why.
  • SSL Certificate: Custom SSL Certificate. Chose the cert you created in ACM
  • Default Root Object: index.html. This is your Angular2 app entry point

Click Create Distribution. Then immediately go back and edit your CF distro. Choose the Error Pages Tab and press Create Custom Error Response.  Fill in the fields like this for BOTH 403 and 404 errors:

screen-shot-2016-10-18-at-2-24-24-pm

This will now support when a URL change is emulated via pushState or directly navigated to when you type in your browser address bar.

You should now be able to go to http://www.yourdomain.com and it will auto-upgrade to SSL https://www.yourdomain.com

Step 4: Point your DNS to CloudFront distribution

Route53 makes this dead simple.  Create a new record set (ex: http://www.rynop.com), type of “A”, alias “yes” and find your CloudFront distro DNS name.  If it doesn’t auto-populate you can get the DNS name from the CloudFront web UI.  It will be something like dsjaaauazh42i.cloudfront.net.

Do this for all your www sub-domains (if you have more than one).

Step 5: Setup redirects to https://www.yourdomain.com

We want http(s)://rynop.org, http(s)://rynop.com, and http(s)://www.rynop.org to re-direct to https://www.rynop.com

To do this we will leverage S3 static website hosting redirects to another hostname and another CloudFront distro to handle the SSL.

Create one S3 bucket, it can be named anything but I recommend making it your root apex domain name, ex: rynop.com

In the bucket properties, select Static website hosting > Redirect all requests to another host name and specify your http://www.domain in Redirect all requests to. Ex: https://www.rynop.com

On the bucket properties page, there is an Endpoint domain for the bucket. Copy this to your clipboard.  Its in the format rynop.com.s3-website-us-east-1.amazonaws.com

Screen Shot 2017-04-20 at 1.21.14 PM

Now go make a 2nd CloudFront distribution of type “web”.  For the Origin Domain Name paste the Endpoint domain from your clipboard. Leave Origin Pathblank. SetViewer Protocol Policy toRedirect HTTP to HTTPS .  For Custom SSL Certificate chose the cert you created in ACM.  Leave Default Root Object blank.

No go back into Route53, and create “A” record set of type “alias” for each domain that needs to be re-directed. Select the S3 bucket from the “alias target” dropdown. It may take a while to populate. Ex: I created A record set aliases for rynop.com,rynop.org,www.rynop.org

Step 6: Verify SSL and re-direction

You can now validate the re-direct by running
curl -v "http://rynop.com"

You should get a 301 re-direct.

You can then put any of the domains you configured in your web browser and you should see the helo world html page.

Step 7: Compress and upload your Angular2 app

I am a huge fan of the angular-seed project by Minko Gechev. I have created a gulp plugin that automates the steps below, but essentially it does:

  1. Gzips your built assets (html,css,js)
  2. Upload all assets to s3 with the HTTP headers to allow CloudFront edge caching:
    aws --profile rynop s3 cp . s3://www.rynop.com/ --recursive --include "*" --acl public-read --cache-control public,max-age=31536000,no-transform

    These cache-control header values tell browsers and proxy servers it is OK to cache without modification, and CloudFront should cache for 31536000 seconds.

  3. Invalidate index.html in CloudFront (you need to do this for all your assets, not just index.html):
    aws cloudfront create-invalidation --distribution-id S11A16G5KZMEQD --paths /index.html

I highly recommend gzipping your assets BEFORE uploading to s3.  CloudFront supports runtime gzipping however CloudFront will not gzip if its busy.  All the consumers of your website have supported gzip compression for the last 15 years so you don’t have to worry about the Very: header.

Summary

You now have a single page app, who’s static assets have full HTTP/2 support, 11 nines of durability, infinite scale, free SSL, cached at the edge all over the world with zero servers to maintain and no ongoing renewals.  You are ONLY charged for bandwidth and HTTP requests out of CloudFront (and the infrequent calls to your S3 origin).

Lots of setup here I know, but remember this is one time only and you are getting some world class features for almost nothing.

Next steps

If you would like the webdev’s dream setup, I highly recommend checking out the Serverless framework for your backend (disclosure: I’m one of the original co-creators).  It is open source and a completely kick ass way to create and manage an AWS backend that leverages Lambda and API gateway.

Advertisements