Solving the SPA Refresh Problem for React Apps Hosted on AWS S3 and CloudFront
When building single-page applications (SPAs) with React and hosting them on AWS S3 and CloudFront, you might encounter a common issue: refreshing a page or directly accessing a route other than the home page results in an error. This happens because the server doesn’t know how to handle client-side routes. In this blog post, I’ll walk you through the steps to properly configure your AWS setup to ensure your SPA works seamlessly, even on refresh.
The Problem
If you’ve hosted a React SPA on AWS S3 and CloudFront, you might have seen this error:
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>D1QH07X67AYNWZ54</RequestId>
<HostId>azBROMZLMBqfRfRhTIUrIShpCJyFyhBR5FTnH+SBRhSe4xLMRIVjqUhcwMwLu/H1O6zTrOu3tU4=</HostId>
</Error>
This typically occurs when you refresh a page or try to directly navigate to a route other than /
. The reason for this is that the server (S3 in this case) is trying to find a file corresponding to the route, but SPAs rely on client-side routing, which the server is unaware of.
The Solution
To solve this, we need to configure our S3 bucket and CloudFront distribution to always serve index.html
for any route, allowing the client-side router to handle the navigation. Here’s how you can do it:
Step 1: Configure S3 Bucket
-
Enable Static Website Hosting:
- Go to your S3 bucket in the AWS Management Console.
- Click on the
Properties
tab. - Enable
Static website hosting
. - Set both the
Index document
andError document
toindex.html
.
-
Set Bucket Policy:
Ensure your bucket policy allows public read access.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
Step 2: Configure CloudFront Distribution
-
Set Default Root Object:
- Go to your CloudFront distribution in the AWS Management Console.
- Under
General
, set theDefault Root Object
toindex.html
.
-
Edit Behavior Settings:
- Navigate to the
Behaviors
tab. - Select the behavior for your default cache behavior (usually
/
). - Click
Edit
. - Ensure the following settings:
- Viewer Protocol Policy: Redirect HTTP to HTTPS or HTTPS Only.
- Allowed HTTP Methods: GET, HEAD, OPTIONS.
- Cache Based on Selected Request Headers: None.
- Object Caching: Use Origin Cache Headers.
- Forward Cookies: None.
- Query String Forwarding and Caching: None.
- Navigate to the
-
Custom Error Responses:
- Go to the
Error Pages
tab. - Create a custom error response:
- HTTP Error Code: 404
- TTL (seconds): 0
- Custom Error Response: Yes
- Response Page Path:
/index.html
- HTTP Response Code: 200
- Go to the
-
Invalidate CloudFront Cache:
- Go to the
Invalidations
tab. - Click
Create Invalidation
. - Enter
/*
to invalidate all cached files. - Click
Invalidate
.
- Go to the
Step 3: Clear Browser Cache
Your browser might cache the old version of the site. Clear the cache or use an incognito window to test the application.
Conclusion
By following these steps, you ensure that your React SPA can handle direct URL access and page refreshes correctly. This configuration tells AWS to always serve the index.html
file for any route, allowing your client-side router to take over and display the correct view.
Deploying SPAs on AWS can be tricky, but with the right configuration, you can provide a seamless user experience. Happy coding!
Feel free to reach out if you have any questions or need further assistance.