How we deploy SPAs on S3 and CloudFront

Here at Pretto, we chose early to have a clear client / server separation. Our product is composed of several independent single page apps, backed by a single monolithic API. There is a little development overhead, but fast prototyping, explicit decoupling, and fast rebuilding largely makes up for it.

We are a small team, and every minute spent on opts is not spent on moving towards product-market fit. That’s why we chose one of the simplest deployment strategies : static hosting on the AWS stack.

In this article, we will guide you through the steps we follow when deploying a new app.

Build your app

First, you need to make sure that your frontend development stack can generate a static build of your app. Please don’t spend hours tweaking your webpack config, mature libraries like create-react-app or vue-cli have already done it better than you could.

Once yarn run build runs locally and generates you a static build, you’re ready to setup ops.

Setup a S3 bucket

Head over to your aws console and create a new bucket. You need a unique bucket name, and — guess what — your apps domain should be unique too so use that.

Ignore all other options, we will get to them soon enough, and just create your bucket.

It is now time to deploy our apps content. AWS offers us an interface but we want to automate this step, so install the AWS CLI and create a deployment script.

(Note that AWS CLI commands are done as an authenticated user. We will assume your root account is doing it in order to keep the examples simple, but depending on your security policies, you may want to use a dedicated CI account with more limited permission. As a reference, here is our CI policy for bucket syncing via the CLI)

If everything goes well, your bucket should be populated.

Congratulations! This is not the end though. You now need to enable static hosting so your bucket has a domain of its own. This can be done in the Bucket Properties tab

Enable the static hosting, and beware of redirecting every request to index.html. This will allow your HTML5 client-side routing to take the wheel on hard reloads.

Visit the endpoint created for you by S3 and voilà! Your app is up and open to the world!

While you’re in the prototyping phase this can do the work. You won’t pay much, and your deployment script is ready. However, once you move on to production, you may want to use a custom domain and optimise response times. We stay in the AWS stack, and solve these issues with their CDN offering, CloudFront.

Enable CloudFront, and use a custom domain

Go to your cloudfront dashboard, and setup a new WEB distribution

Origin Settings

Set the S3 host as the Origin domain name.

Default Cache Behavior Settings

Enable HTTP to HTTPs redirection (because we want to be secure), and automatic object compression if your build step does not do it for you.

Distribution Settings

Add your domain as a CNAME, use a default certificate for now (we will setup a custom one soon), and set index.html as your default root object.

Setup a SSL certificate

While your distribution is being deployed, go to the ACM dashboard in order to setup a certificate (beware, even if your bucket and distributions are in europe, your certificate needs to be in N. Virginia)

Request a certificate for your domain, and validate your request following the instructions in your confirmation email (sent to the owners of the root domain). You should now have an enabled certificate for your subdomain. Let’s add it in cloudfront !

Go back to your cloudfront dashboard, specifically to the settings page for your distribution. Open the general tab, and click edit.

Enable the custom SSL certificate, choosing the one you just created in the list, and save your changes.

Finally, you want to setup your domain so that it points to your cloudfront distribution. The GUIs vary from one to another, but basically you want to add a CNAME record, pointing to the reference cloudfront domain ( in that case).

It may take a few minutes for the domain to propagate, but you’re essentially done. Congratulations, your app is now live, and it will handle all the traffic you can throw at it before price even becomes an issue :)