If you have ever deployed a Single Page Application (SPA) or a static site using Amazon S3, you might have encountered a frustratingly common issue: refresh errors. You navigate to yoursite.com/about, and everything works perfectly. But the moment you hit the refresh button—or share that link with a friend—you are greeted with a glaring 403 Forbidden or 404 Not Found error.
This is one of the most infamous pitfalls of static hosting. Here is why it happens and the definitive way to fix it.
The Root Cause
S3 is an object storage service, not a web server.
When you upload a React or Vue app, you usually have a single index.html that acts as your application shell. Your app uses client-side routing (like React Router) to handle navigation. However, S3 does not know about your routing logic. When you request /about, S3 looks for a literal file named about in the root directory. Since that file does not exist, S3 returns a 404 error.
To solve this, you need to instruct S3 to serve index.html for all requests, regardless of the path. This is usually done via the "Static website hosting" settings.
The "Old" Solution: S3 Redirection Rules
The standard advice is to use the S3 "Static website hosting" feature and set the "Error document" to index.html. This creates a redirection rule.
While this works, it has a significant downside: HTTP status codes.
When S3 returns index.html for a missing page, it often sends a 200 OK status. This is fine for the user interface, but it is a nightmare for SEO. Search engines cannot distinguish between a valid page and a missing page, and analytics tools fail to track 404s correctly.
The "Solved" Solution: CloudFront and Custom Error Responses
The most robust and professional solution involves CloudFront, AWS's Content Delivery Network (CDN). Instead of relying on S3's redirection, we use CloudFront's custom error responses to serve the index.html file while preserving the correct HTTP status.
Step-by-Step Implementation
Step 1: Set up the S3 Bucket
Upload your static files. Ensure the bucket is configured for static website hosting (or use the newer S3 website endpoint). For CloudFront, it is often better to use the REST endpoint to avoid redirect issues.
Step 2: Configure CloudFront Behavior
In your CloudFront distribution settings, find the "Error Pages" section.
Step 3: Create a Custom Error Response
You need to configure rules for specific HTTP error codes. Add a rule for both 403: Forbidden and 404: Not Found.
For each error, set the following:
Customize Error Response: Yes
Response Page Path: /index.html (or the root of your app)
HTTP Response Code: Set this to 200 only if you want to hide the error. However, the best practice for SEO is to set this to 404 if it is a real missing page, or leave it as 200 if you are using a SPA router.
The SPA Trick: For SPAs, you usually want the error response to be 200 (so the app loads and the router takes over). But, if you want to be precise, you can implement server-side logic to determine if the file exists. For most SPAs, the 200 override is the accepted standard.
The "S3 Redirects" Caveat
If you were using the S3 static website hosting endpoint (with s3-website-region.amazonaws.com), you might have been using RoutingRules to handle redirects. This approach has been largely superseded by CloudFront because:
HTTPS: S3 website endpoints do not support HTTPS natively. You must use CloudFront for SSL certificates.
Speed: CloudFront serves content from edge locations, drastically reducing latency.
Granularity: CloudFront allows you to handle errors without changing the URL in the browser, whereas S3 redirects often perform an explicit redirect (changing the URL).
The Ultimate Configuration
If you are using CloudFront + S3, your configuration should look like this:
S3: Just store the files. Do not rely on the "Static website hosting" redirection feature. Use the bucket's REST endpoint (not the website endpoint).
CloudFront Origin: Point to the S3 bucket REST endpoint.
Error Pages:
Error Code: 403
Response Page Path: /index.html
HTTP Response Code: 200
Error Code: 404
Response Page Path: /index.html
HTTP Response Code: 200
A Note on http-errors and Nginx
Sometimes, developers use the http-errors middleware in Node.js or Nginx configurations to serve the index.html file. The logic is identical: you are creating a "catch-all" route.
If you are using a reverse proxy like Nginx in front of your S3 bucket (via a proxy pass), the equivalent configuration is:
text
try_files $uri $uri/ /index.html;
This tells the server to check if the file exists ($uri), and if not, fall back to index.html.
Why This is "Solved"
By using this CloudFront-first approach, you get:
Perfect SPA Routing: Deep links and refreshes work flawlessly.
Global Speed: Edge caching.
Security: Full HTTPS support.
SEO Friendliness: You control exactly which status codes are returned.
So, ditch the S3 static hosting redirect rules and embrace CloudFront. It is the standard, production-grade method for hosting modern web applications on AWS.
Top comments (0)