Router and React Application in AWS S3

Some days ago, I created a React application that I wanted to deploy on AWS S3. I followed all the steps described in this article, but when I tried to use some advanced features like routers, I ended.

React Router

The Router allows me to have different URLs to manage different parts of my application. I may have /vehicles for the search page of the vehicles. /vehicles/<id> for the details page of a single vehicle, and /vehicles/new for the subscription page of a new vehicle.

To do so, I need to install React Router and configure Webpack.

Let’s first install React Router.

npm install react-router-dom

And now, I need to configure Webpack to indicate the file which handles the routes. I need to modify the webpack.config.js file.

module.exports = {
  output: {
    path: join(__dirname, './dist/my-frontend'),
    publicPath: '/',
  },
  devServer: {
    port: 4200,
    historyApiFallback: true,
  },
  plugins: [...],
};

The thing is that all this works fine in my localhost. I can even copy and paste a URL and the React application loads the adequate view.

However, after deploying in the AWS S3, I encountered the following error: Access Denied with a 403 error code.

AWS S3 object not found error

Why?

When working in localhost, I have Webpack which acts as a web server. But when working in AWS S3, all the application is stored in the index.html file. When I try to access another page, AWS S3 tries to load a different static file, which does not exist.

The key is that I’m missing a web server. But all my React application fits into the index.html file, so I don’t need one. How can I solve this?

CloudFront

AWS CloudFront is a CDN, a Content Delivery Network. It’s a service which distributes my content, my React application in this case, all around the world. This way, I have a faster download time when viewing my application in France or the USA.

A common use case of AWS CloudFront is creating a distribution and having an AWS S3 bucket as the origin. This way, AWS CloudFront distributes the content of the AWS S3 all around the world.

But now, I need to configure my AWS S3 bucket to allow my AWS CloudFront distribution to read from it.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipalReadOnly",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::my-bucket/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::1234567890:distribution/ABCDEF98765"
                }
            }
        },
        {
            "Sid": "AllowEveryoneReadOnly",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::my-bucket/*"
        }
    ]
}

I still need to make my content public, as described in my previous article.

But this use case does not solve my problem.

Error Pages

However, AWS CloudFront has a configuration that solve my problem.

AWS CloudFront error pages configuration

After creating my distribution, I can add some behavior for the error pages. What I want is that when the page is not found, it loads the index.html file. But I have to do it using a 200 HTTP response code, otherwise, the browsers or the Search Engines will consider this behavior an error.

If I have some authentication in my application, I must also do the same redirection when a 403 HTTP error code occurs.

The problem with the URLs is now solved. But I have a new problem now. As I’m using a CDN, it stores the content of the AWS S3 bucket in several places around the world. Caching the content.

But when I need to update my application, I need to tell both AWS S3 and AWS CloudFront that the content is new.

In the AWS CloudFront panel, I have an invalidation tab to revoke the cache on some files. But I need a way to do it from a CI/CD pipeline.

CI/CD

I can’t have manual operations to update my frontend application. I need to update my frontend from a CI/CD platform such as Github Actions or GitlabCI.

I can add this configuration to AWS S3. When I upload the content to AWS S3, I can indicate the cache control for each file.

So, I need to tell that all the files must be cached as long as possible. But the index.html shouldn’t. Here are the commands:

aws s3 sync --delete --cache-control 'max-age=604800' --exclude *.html . s3://my-bucket
aws s3 sync --delete --cache-control 'no-store, no-cache' . s3://my-bucket

The first line uploads the content of my frontend to the AWS S3 bucket. It indicates the age of the cache (7 days). But with this first line, I upload the files excluding the HTML files.

And the second line, I upload the rest of the files and set the cache control to no-cache.

Conclusion

With the configuration shown, AWS S3 + AWS CloudFront, my frontend application is faster than just using AWS S3. I don’t even need to configure my AWS S3 as a static website, as this is done with AWS CloudFront (which is a static website by default).


Never Miss Another Tech Innovation

Concrete insights and actionable resources delivered straight to your inbox to boost your developer career.

My New ebook, Best Practices To Create A Backend With Spring Boot 3, is available now.

Best practices to create a backend with Spring Boot 3

Leave a comment

Discover more from The Dev World - Sergio Lema

Subscribe now to keep reading and get access to the full archive.

Continue reading