Why CloudFront & S3 is better for hosting static sites?
AWS CloudFront is a CDN that can be used to serve static HTML sites backed by S3 storage.
S3 storage is very cheap. Combined with CloudFront, you can make your sites serve in low latency speeds.
Deploy to S3 and start CloudFront cache invalidation
Why automated deployment?
Automated deployments allows your changes to be made available on the live site instantly and automatically whenever you push to the repository.
You can forget about copying the code manually and uploading to your S3 bucket.
Why trigger cache invalidation?
Once the files are copied to S3, we need to trigger an invalidation for the cache in CloudFront. Otherwise, CloudFront will continue to server the old content from its cache.
We’ll see examples and code snippets for both Bitbucket and Github below.
Using Bitbucket Pipelines
Pipelines is Bitbucket’s CI/CD tool.
Steps to setup deployment:
- Create a file named bitbucket-pipelines.yml in the project directory.
- Paste the below code in the file and give your AWS_Access_key_ID, AWS_Secret_access_key, $CloudFront_Distribution_Id values through
- After adding your AWS key save and push your changes into the bitbucket repo, it is done!
image: node:10.15.0 pipelines: default: - step: name: Deploy to S3 deployment: production script: - pipe: atlassian/aws-s3-deploy:0.4.4 variables: AWS_ACCESS_KEY_ID: $AWS_Access_key_ID AWS_SECRET_ACCESS_KEY: $AWS_Secret_access_key AWS_DEFAULT_REGION: 'us-east-1' S3_BUCKET: 'jiga.com.au' LOCAL_PATH: $BITBUCKET_CLONE_DIR ACL: 'public-read' - step: name: Invalidate CloudFront cache script: - pipe: atlassian/aws-cloudfront-invalidate:0.3.3 variables: AWS_ACCESS_KEY_ID: $AWS_Access_key_ID AWS_SECRET_ACCESS_KEY: $AWS_Secret_access_key AWS_DEFAULT_REGION: 'us-east-1' DISTRIBUTION_ID: $CloudFront_Distribution_Id
You can add the variables directly in the YML file but it is recommended to add them through ‘Repository variables’ under Pipelines Settings in Repository settings:
Using GitHub Actions
Actions is GitHub’s CI/CD tool.
Steps to setup deployment:
- Create a file deploy-to-s3.yml in the project directory.
- Add the required variables to secrets
- After adding your secrets, push your changes into the Github repo and see the magic!
name: Deploy Website 'on': push: branches: - master jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Deploy to S3 uses: jakejarvis/s3-sync-action@master with: args: '--acl public-read --delete' env: AWS_S3_BUCKET: '${{ secrets.AWS_PRODUCTION_BUCKET_NAME }}' AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' AWS_REGION: '${{ secrets.AWS_REGION }}' SOURCE_DIR: build - name: Invalidate CloudFront Cache uses: awact/cloudfront-action@master env: SOURCE_PATH: ./public AWS_REGION: us-east-1 AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' DISTRIBUTION_ID: '${{ secrets.DISTRIBUTION_ID }}'
Now that you know how to automatically deploy to S3 on every change to the repository, you can learn some two tricks about routing options with S3 hosting.
Route pages without index.html filename suffix
By default, CloudFront expects the full URL to match with the actual path in S3. For example, if you have a path <bucket_url>/<subdirectory>/index.html
as below, you will need to open https://example.com/subdirectory/index.html
in your browser to open the file.
If we try to open the URL without index.html
suffix, we will get AccessDenied
error like below.
But there is a small trick that we can apply to open just https://example.com/subdirectory
from your browser and let CloudFront/S3 serve the index.html automatically.
From an SEO perspective, it is also good to strip out the HTML suffix anyway, because the overall URL length is smaller which search engines like.
So here is the trick, the Origin Domain Name
has to be updated in CloudFront in the following format:
Click ‘Edit’:
Change the Origin Domain Name
from bucket.s3.amazonaws.com
to bucket.s3-website-us-east-1.amazonaws.com
(based on your region name)
Once the CloudFront Distribution is updated after the above settings change, try opening the subdirectory URL directly and it will work:
Route pages without *.html filename suffix
It is also possible to route a file such as <bucket_path>/page.html
to an URL example.com/page
. Yet again, this is beneficial for SEO purposes and your URL looks neat.
To get this working, follow these two steps:
- Remove
.html
suffix/extension from your original file either before uploading to S3 or after. - Override the metadata
Content-type
of this file totext/html
. Select the file -> Actions -> Change Metadata. (The default value for files without an extension isbinary/octet-stream
which triggers a download when opened from the browser)
Now you should be able to access your URL without the HTML suffix:
If you have more than one file to update the metadata or if you are deploying from Bitbucket Pipelines or Github Actions, you can automate this update on Metadata. Example step:
- step: name: Update Metadata on HTML files image: fuinorg/atlassian-default-image-awscli:latest script: - >- while read file; do AWS_ACCESS_KEY_ID=$AWS_Access_key_ID AWS_SECRET_ACCESS_KEY=$AWS_Secret_access_key AWS_DEFAULT_REGION=us-east-1 aws s3 cp --content-type="text/html" --metadata-directive="REPLACE" --acl=public-read s3://<bucket>/$file s3://jiga.com.au/$file done <$BITBUCKET_CLONE_DIR/files_to_update_in_s3.txt
files_to_update_in_s3.txt
should contain the list of files that needs to be metadata set. You can generate this file dynamically locally using gulp.
The doctype has to be set correctly in the HTML file for this to work. Example:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> Hello world </body> </html>
Note there is a little difference between the previous subdirectory approach and this one. We will have a trailing slash in the earlier approach and no slash in this one.
MOST COMMENTED
Flutter
Flutter Setup
React Native
Learn React Native with a Board Game (Part 1 of 4)
jQuery / Web Development
jQuery DataTable: Sorting dynamic data
Uncategorized
Hibernate – Associations are not loaded
Database / Java / MySQL / Spring Boot
Hibernate Error – Encountered problem trying to hydrate identifier for entity
Spring Boot / Uncategorized
Working with Hibernate in a multi-threaded application
Web Development
Designing REST APIs