AWS S3 . It is cheap, scalable, and “performant”. Especially when it tag team with CloudFront. Static Website Hosting This is a documentation of how to host a Single Page Application (React for this case) on AWS S3 with SSL over CloudFront using this of mine as an example. pet project 1) The project A simple static site so no redux is used; this setup would also work with redux. So its gonna be react and react router mainly. Here are the specifics: react: ^15.6.1 react-router: ^4.1.2 The bundler I am using is . webpack: ^3.5.5 2) AWS S3 S3 can host static website apart from just storage. that each bucket is meant for only 1 website, that is you cannot have a bucket called and have each directory hosting 1 website. No. It is going to be per website per bucket. Note my-static-websites Set up the static website hosting configuration as such for the bucket. Take note of the . Endpoint This setup is saying: When users visit the root path of my website, show them the file . index.html When users visit the a page that does not exist, show them the default S3 error message on their browser. So when we upload the react project into the bucket: At the root path, users can see the site up and alive!!! Users can also navigate to different paths!!! hitting refresh when the path is instead of will show you a blank screen, or the page if one was setup :(((( But /something / error.html What is happening? Well the path is looking for a file in the S3 bucket but it was not to be found. Since this is a Single Page Application, there is only 1 html file, 1 GOD html file. /something something.html So here is the challenge. We need to map paths to the file. all index.html Since this is a react project, we do not need to map each path to a specific other html page like a typical website; the will load the javascript bundle and react router will get to work to show users the correct page based on the path. index.html Hygiene pages Not sure if this is the correct term for and files but yea you’ll need these files for . These files go into the root directory of your bucket as siblings to the file. and the url to them are, eventually, and respectively. sitemap.xml robots.txt SEO index.html https://www.yourdomain.com/robots.txt https://www.yourdomain.com/sitemap.xml 3a) AWS CloudFront — Distribution CloudFront is the CDN of AWS it can handle the mapping of the routes, on top of caching the site. Start off by creating a distribution. The key configurations I will like to mention are: web Origin Domain Name — Upon focusing on this field, there will be a dropdown listing all the buckets that you have in your AWS S3. Do use any option from this list. Instead, enter the domain of the of the S3 bucket as mentioned in the previous section here. NOT Endpoint static-website-hosting-enabled Viewer Protocol Policy — Select to ensure your website is always viewed over HTTPS, and there is no duplicate instance under the HTTP protocol that is accessible by the public. Redirect HTTP to HTTPS Cache Based on Selected Request Headers — Select and add in the header. This is to avoid any CORS related errors. Whitelist Origin Alternate Domain Names (CNAMEs) — enter the non-www and the www domain name here, or any other subdomain you have may have intended, separated by a line break or comma. SSL Certificate — Select and upload you own ssl certificate, along with the private key and CA bundle via . Custom SSL Certificate Amazon Certificate Manager Compress Objects Automatically — Select . your uncompressed assets from S3 and improved your page speed, by . Exchange all the deep apache/nginx/IIS setups with just a radio button — that’s like . Yes CloudFront will automatically compress Google standards trading a wight for a dragon Create the CloudFront distribution and wait for it to get deployed. Take note of the distribution’s Domain Name . 3b) AWS CloudFront — Error Pages After creating the CloudFront distribution, while its status is , proceed to the tab. Handle response codes 404 and 403 with . In Progress Error Pages Customize Error Response 1 week or 604800 seconds of caching. Google recommends What we are doing here is to set up CloudFront to handle missing html pages, which typically occurs when a user enters an invalid path or, in particular, when they refresh a path other than the root path. When that happens: CloudFront will be looking for a file that does not exist in the S3 bucket; there is only 1 html file in the bucket and that is the for the case of a Single Page Application like this project example index.html A 404 response will be returned and our custom error response setup will hijack it. We will return a 200 response code and the page instead. index.html React router, which will be loaded along with the index.html file, will look at the url and render the correct page instead of the root path. This page will be cache for the duration of the TTL for all requests to the queried path. Why do we need to handle 403 as well? It is because this response code, instead of 404, is returned by Amazon S3 for assets that are not present. For instance, a url of will be looking for a file called (without extension) that does not exist. https://yourdomain.com/somewhere somewhere PS. It used to be returning 404, but it seems to be returning 403 now; either way it is best to handle both response codes). 4) DNS I intend to use the www version of the domain. Go to the DNS zone file and set up as such. This setup indicates: will be redirected to domain.com www.domain.com requests will be rewritten, if valid, from to http https I am using as my DNS service provider, and they come with an option to redirect or to at the DNS level. namecheap.com https http non-www https www However. If your DNS service provider does not provide this function, you can use AWS S3 to do the redirect instead. Create bucket with these settings. another Set the value DNS A record of the root domain to the end point of this bucket. What will be achieved is all request will be directed to this bucket. This bucket will in turn redirect the request to the domain, which points to the bucket where the files are. And yes it will be a . In case you are wondering, . non-www www 301 redirect this is the significance of a 301 redirect Conversion of to will be handled by CloudFront configuration (Viewer Protocol Policy) that was setup previously. http https At this point of time, you should be able to access your site like a normal website. Refreshing at a path other than the root path should also work. All non https requests will be redirected under the https protocol. All non www request will be redirected to the www domain under the https protocol as well. Bots and crawlers should be able to access your and files as usual. robots.txt sitemap.xml 5) Conclusion Pros Financially friendly. You basically pay for what you only use, so you would not be wasting any penny on under utilized resources that comes with a monthly payment model. On top of that, this Cloudfront & S3 combination also saves you some money because it is a lot cheaper to transfer data out to the Internet via CloudFront than S3. Not to mention the better performance of a CDN. Scalability friendly. If somehow your site gets really popular, there will not be a scalability issue due to the surge in traffic because AWS CloudFront will be taking care of that for you. There is no need for any upgrade of plans with other hosting companies. Performance friendly. Since this whole site is sitting on top of a CDN, delivery of the site and the assets are going to be super fast. DDoS unfriendly. Since the site is behind AWS CloudFront, is, once again, handled by CloudFront. DDoS attack are guarded against by Amazon’s own technology and I will place my bets on their cyber security technology and reliability than on other hosting companies. attack against DDoS Security friendly — Since CloudFront is now handling the SSL configurations, you will see that the SSL tests for your domains are A grade on . SSLLabs Cons This works only on static sites. It will take a humongous amount of traffic to even slow down a static website substantially. Most of the bottle necks in a typical application is when it interacts with a backend that involves logic computation and database queries. Since the site is cached on the CDN, any changes will not be seen immediately and have to wait until the cache expires. This is something that comes along with any caching mechanism. We can mitigate it by invalidating cache (which will incur charges). If your javascript file names are hashed, then you can ignore the the javascript files and just need to invalidate the file. Alternatively, you can give a lower caching period for the file. index.html only index.html Every Single Page Application’s bug bear is the requirement for server-side rendering. Bots and crawlers are not able to get the meta data of the site because they do not allow javascript to execute, apart from . So if your site is only concerned about SEO on Google, this setup is good to go. But if you are reliant on other search engines, or if you are marketing the site via social media like Facebook, this is not ideal. (TODO serve html pages using API Gateway and Lambda) Googlebot As all 404 and 403 responses are hijacked to return 200, you will probably not receive any 404 errors on Google Search Console (GSC), if you had indexed your website there. These 404 reports provided by GSC are useful to tell you which pages are having error and will notify you about it. Without them you will not know which pages are down or if there are any broken links linking to other parts of your website. Side quest In this section of the article, I will be documenting how to automate the deployment process of such a site in such a setup from just the command line. 1) AWS IAM To start off, you will need to create an IAM user and give it the necessary S3 permissions. Note the and the , as well as the . access key id secret access key User ARN IAM users are access control configuration in your AWS account, principally to answer the question of who can do what to which of the services under your account. Let’s call this user . iam_user 2) AWS S3 Change the bucket policy to allow this to make changes to the bucket. iam_user {"Version": "2012-10-17","Id": "someID","Statement": [{"Effect": "Allow","Principal": {"AWS": "arn:aws:iam::123456789:user/iam_user"},"Action": "s3:*","Resource": "arn:aws:s3:::bucket-name"}]} 3) Deployment As this is a simple, mostly static, website, there is no testing scripts or any CI server set up for the deployment procedure. It will just be a simple task to upload new files to the correct bucket in S3 using . AWS CLI Cleanup But before uploading, make sure you clean up the distribution folder where you build your files for the production environment. Since I use as my bundler, I utilise the to help me dispose of old files before building new ones. This is to prevent uploading the same old assets again to the bucket. webpack clean-webpack-plugin # webpack.config const CleanWebpackPlugin = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')const pathsToClean = ["dist"]const cleanOptions = {} ... output: {path: path.resolve(__dirname, "dist", "assets"), // all files are bundled into the dist/assets sub-directorypublicPath: '/assets/',filename: 'bundle.js'}, ... plugins: [...,new CleanWebpackPlugin(pathsToClean, cleanOptions), // cleanup the whole "dist" foldernew HtmlWebpackPlugin({template: "./src/index.production.html",filename: "../index.html" // all files are bundled into the dist/assets sub-directory, but index.html will be placed 1 directory up in the dist directory itself}), ...] Uploading Now to upload the files to S3. To prevent any Tom Dick and Harry from being able to do so, authentication is required. This is where all the work for IAM comes into play. We will use a script to do the uploading, with custom configuration to authenticate the request. You can use flag to test your script before actually doing the upload. This is the final version of my script. --dryrun aws s3 cp ./dist s3://better-cover-letter --recursive --exclude "*.DS_Store" --acl public-read --cache-control public,max-age=604800 --dryrun --profile iam_user The flag is to prevent the upload of the irritating, ever present file in macOS. --exclude .DS_Store The flag will set the access control level of the files. Make it public readable so people can access your site, otherwise they will be slapped with a message. --acl 403 Forbidden The flag adds the cache-control header to the S3 objects when Cloudfront calls for them. These cache control headers will be passed to the browser to and thereby increasing page speed. 604800 is 1 week in seconds, so this value will cache these assets for a week. --cache-control leverage on browser caching max-age [Google] recommend[s] a minimum cache time of one week and preferably up to one year for static assets, or assets that change infrequently The flag is used to set the specific IAM user credential to authenticate this operation. As I am using this same macbook pro for my work and my personal projects, I have multiple AWS accounts to handle, thus the need for this flag to differentiate the different IAM users. Check out for more information. These are my config and credentials files for your reference. --profile AWS CLI named profiles # ~/.aws/config[default]region=us-west-2output=text # ~/.aws/credentials[iam_user]aws_access_key_id=somethingaws_secret_access_key=something [company_user]aws_access_key_id=something_elseaws_secret_access_key=something_else The and are specific to the that was created. aws_access_key_id aws_secret_access_key iam_user Once you are ready, you can remove the flag and do a test run to ensure that your files are indeed uploaded to the correct bucket. Yes, a run. It is not the end of the deployment step. We can go further to completely automate the whole process. --dryrun test : AWS S3 does not charge data transfer in to the bucket, only out. So feel free do spam deployment. (In fact, S3 does not charge data transfer out to Cloudfront.) NOTE Combine the Steps As it stands now, we have to build our site first using to generate the files, then upload the files using the command. webpack -p — config webpack.config.js aws s3 cp To make our life better, we can create a new script command to run these commands one after another, without having us to be there waiting for the first command to finish then manually execute the other. # package.json ..."scripts": {..."deploy": "webpack -p --config webpack.config.prod.js && aws s3 cp ./dist s3://better-cover-letter --recursive --exclude "*.DS_Store" --cache-control public,max-age=604800 --dryrun --profile iam_user"...} So just run and these will happen in chronological order. npm run deploy Old production files are cleaned up by clean-webpack-plugin New production files are compiled into the folder (based on my webpack config file) dist The production files are then uploaded to S3 and ready for access once the cache in the AWS Cloudfront CDN expires. There it is, the fully automated process for uploading the static website. More Housekeeping (Optional) If you are bundling your javascript files with a hash like me, you will find your S3 bucket accumulating with old js file instead of getting replaced by the new ones since they are different files by virtue of the hash in their file name, eg . Not so much if you are uploading just which will replace any present in the bucket. bundle-0af19d01880334b789.js bundle.js bundle.js Since storing files in S3 isn’t free, albeit not that expensive either, its still wise to remove files that you will never be using again. So we can use AWS CLI again to do a removal of these old js files before upload (note: I am leaving the files in the root directory of the bucket untouched, just cleaning up the folder). assets aws s3 rm s3://better-cover-letter/assets --recursive --profile iam_user --dryrun Once again, combine them in the deploy script.