Our method to deploy static Single-Page Applications
Our SPAs are mostly Vue.js applications. We deploy them with the following architecture:
- AWS S3 static website hosting
- AWS CloudFront CDN + SSL resolver
- Cloudflare DNS
Although all of this is super simple to setup, there is weird stuff that is good to know whenever you want to deploy a new website this way. Read very thoroughly the instructions in this section, because any deviation from the proven way of doing things can quickly break your setup.
Create a bucket to host the website. Preferably name it as per the host under which it will be accessible (e.g.,
cars.seraphin.be). Do not enable the "use this bucket to host a website" option, as it would make the bucket accessible directly, bypassing Cloudfront. We do not want this as Cloudfront can enforce security policies for the bucket (e.g. geographical restrictions). Allowing the bucket to be accessed directly would defeat this entire purpose.
Once the bucket is setup, you need to grant it admin access for deployment purposes. For this, go to the AWS IAM console, then to Policies, and update the
s3-spa-deployerpolicy to include the 2 new lines under the
Don't worry about the "version" flag, it has no impact and is outdated even on AWS blogs. Note that both the bucket and its content must be allowed (hence the line with
/*after the root resource).
Save the policy and ensure it is attached to a Group to which your user has access (typically,
Note that you need to have
aws-cliinstalled and that your AWS user should be stored in
aws_access_key_id = your_access_key_here
aws_secret_access_key = your_secret_access_key_here
region = eu-central-1
You know have an AWS profile fully setup on your local machine, and you'll be able to use its privileges to upload your SPA files to the S3 bucket.
You can now use a script along the following lines to deploy to S3 (adapt for your own bucket name) :
"build": "node build/build.js",
"build-production": "NODE_ENV=production npm run build",
"preproduction": "npm run build-production",
"production": "aws s3 cp dist/ s3://bucket-name --recursive --profile seraphin"
This script should typically be included in your
package.jsonfile. Note the
--recursiveflag to let
aws-cliknow that the whole set of files should be uploaded and the
--profile seraphinthat specifies the profile to use.
To ensure your site runs on SSL, you need the AWS Certificate Manager to either generate a cert for you or host a pre-generated cert. We at Seraphin use an existing certificate that has been uploaded. Open the AWS Certificate Manager in the US East (N. Virginia) region and make sure there is a certificate available covering the required domain.
CloudFront will only work with certs from the US East region! This is weird but good to know.
Create a new distribution on CloudFront, specifying the S3 URL endpoint as the origin. Under CNAME, indicate the requested domain (e.g.,
cars.seraphin.be). If multiple domains redirect to the same CloudFront distribution (e.g.
www.auto-assurance.be), you can add multiple CNAMEs separated by a coma. Ensure you select "Redirect HTTP to HTTPS". Finally, make sure that you input
index.htmlas the root object for the distribution (so that when the bare URL is input, this file gets loaded).
Note the CloudFront domain name (e.g.,
qsdiodazio.cloudfront.net), you will need it for the DNS setup.
Similarly, note the CloudFront distribution ID, as it will be used for the deployment script to clear the cache by creating a cache invalidation at each deployment.
Finally, setup custom error pages to handle your SPA routes: if one tries to visit, say, the
/commentsroute, CloudFront will initially try to serve the
commentsobject from S3. Since this object does actually not exist (because in a Vue.js SPA all we have is an
index.htmlfile), this will yield either a
404error depending on the permissions you have setup. To circumvent this, we must tell CloudFront to serve the
/index.htmlpage for any and all of these errors, replacing the status code by
200. This way, CloudFront will actually still pass the route path to Vue.js, which will be able to serve the correct router component accordingly.
Go to the Cloudflare DNS console, and create a new CNAME, redirecting your domain (the CNAME indicated in CloudFront) to the CloudFront domain (e.g.,
qsdiodazio.cloudfront.net). Make sure to click the little cloud so that it becomes grey: Cloudflare should only be used as a DNS for this, because otherwise it would be redundant with CloudFront's CDN and SSL resolution capabilities.
Do not click on anything else in the Cloudflare console: this thing is way too easy to break
If the little cloud is orange (Cloudflare used as proxy), then unexpected things could happen and your browser could throw an error when navigating your website.
After some time (from 10 minutes to a few hours), the CloudFront distribution should now be active, and you should be capable of navigating a nice shiny SSL-enabled website, all hosted on S3. Well done!
The DNS propagation of CloudFront + S3 can be quite slow, and there is a temporary redirection in place that crashes the SSL checks of your browser. Don't worry, it will disappear in due time. You can check that it is temporary by querying your website with
curland inspecting the response text:
curl -i https://bucket-name.com