

I would like to present these aspects and demonstrate the possibility of how you can deploy a modern app to AWS, the right way.
In this article, you will learn how to deploy your modern PWA application on AWS using S3 for storing the content, CloudFront for distributing the application, and Lambdas functions for handling push-state URLs and server-side rendering. Also, we will explain what to look for in terms of how PWAs and CloudFront cache work.
First of all, you may ask which service you should use in front of your app, and why choose CloudFront? In one of our previous blog post, we outlines the benefits of using a CDN like CloudFront, highlighting improved performance and scalability.
But what else does it give us? CloudFront is a powerful tool, it allows associating special lambda functions called Lambda@Edge. They give us full control over how requests are handled under the hood and what is returned to the browser. We can use them to achieve attractive URLs without ending extensions and to provide pre-rendered HTML pages for search bots.
Lambda@Edge functions allow us to modify the request/response cycle during any of the following stages:
As far as the CloudFront cache is concerned, the function marked as origin request is only executed if there is no data in the cache, while the viewer request function is always run.
There is no doubt that your app should be easily indexable by many search engines since we want to allow as many people as possible to reach it. Unfortunately, some engines use pretty basic crawlers which are only capable of crawling static HTML sites, and in doing so, ignoring all javascript code. To deal with this situation we need to use server-side rendering. SSR is a method of providing a fully rendered page to the client.
The crawler makes a request to the CloudFront service and the latter forwards it to a third-party service, such as Rendertron or prerender.io, which generates a static HTML page. To increase load speed we should cache rendered pages and make sure that SSR is only used for search engine requests.
CloudFront caches responses against the request headers it sends. It does not forward the user request’s User-Agent to the origin, therefore limiting the header’s data, hence decreasing the number of hits to the origin.
So how do we detect a crawler?
We still need to forward the User-Agent header to the origin, but we reduce the number of unique values.
Instead of the real value of this header, we will use our own value. We set it to SSR if the header matches any whitelisted search engines, otherwise, we simply set it to CloudFront. In doing this we only have two possible values of this header.
First of all, we need to add a User-Agent to the whitelisted headers in our CloudFront distribution.
We can do that via the AWS dashboard:
CloudFront Distributions > <your distribution> Behaviors > Edit > Whitelist Headers > Enter a custom header User-Agent > Add Custom
Since this function always runs with the client's request, it shouldn’t be necessary to call on any external services or resources.
Let’s write the first Lambda function to detect crawlers:
That’s it!
We now know which requests come from search engines. Now we are able to continue to the next step of pre-rendering the content for those requests.
This dynamic rendering app was created to serve the correct content to any bot that does not render or execute JavaScript. Since it’s a standalone HTTP server, it’s easy to use and integrate with existing projects. There is a fully-functional demo service which is great for exploring this tool at the beginning. For production usage, consider using your own instance of this service.
The next step is to create a Lambda function which, depending on the user agent set, will return the original response or ask Rendertron for the static HTML page version and then send it back to the client.
We are going to write a function that will be executed before CloudFront forwards the request to the origin.
Now that we know how to properly configure CloudFront and what Lambda functions should contain, it’s time to connect all the pieces together and deploy our application. Instead of creating all required resources manually, we will automate this process using a great tool from AWS — Cloud Development Kit. It allows us to model our infrastructure and provision the app’s resources using, among other languages, JavaScript. It’s easy to prepare reusable modules divided into logical parts or larger, high-level app components that describe the entire app environment.
Here is a list of the AWS resources that we need to create our static application:
Via the following URL, you can find ready-to-run code that will automatically build necessary resources for our app.
All that’s left is to go through the step-by-step instructions from this repo and upload the application’s content to the appropriate S3 bucket.
In the command output, you’ll see a dedicated CloudFront URL where your app will be accessible.
A solid understanding of the operation and mechanisms of the CloudFront service is key to launching a fast and efficient web application. Thanks to this and other AWS tools, such as CDK, we do not have to focus on the architecture or the deployment process. We only need to be concerned about what we are most interested in — the application we plan to run.