Overview
While deploying a static documentation site using AWS S3, CloudFront, and Cloudflare, I came across several edge cases that weren't immediately obvious from the documentation. This post summarizes the issues, their root causes, and the solutions that worked. I'm documenting these as technical notes for future reference, and hopefully they'll be useful to others facing similar problems.
🛠️ Lessons & Gotchas
Lesson 1: Local Redirect Caching
Issue
HTTPS configuration and redirect changes worked correctly in Incognito mode, but normal browser sessions continued to show outdated redirects or "Not Secure" warnings.
Root Cause
Modern browsers cache 301 redirects and HSTS policies aggressively. Local cache can continue serving stale routing information even after CloudFront and DNS changes have propagated.
Resolution
- Clear the domain's HSTS policy using
chrome://net-internals/#hsts. - Perform a hard reload (
Ctrl + Shift + R/Cmd + Shift + R) with DevTools open. - Validate changes using an Incognito window before troubleshooting infrastructure.
Lesson 2: S3 REST Endpoints Do Not Resolve Subdirectory Indexes
Issue
The following requests produced different results:
-
/iplusflow→403 Forbidden -
/iplusflow/→ Object download -
/iplusflow/index.html→ Expected HTML response
Root Cause
CloudFront's Default Root Object applies only to the distribution root (/). When using the S3 REST API endpoint as the origin, S3 treats every request as an object lookup and does not automatically resolve directory requests to index.html.
Resolution
Implement URL rewriting within CloudFront or use the S3 Static Website Endpoint when directory-style routing is required.
Lesson 3: CloudFront Function Execution Stage
Issue
Attaching a CloudFront Function resulted in:
503 Service Unavailable
response.statusCode is missing
Root Cause
The function was associated with the Viewer Response event while attempting to modify the incoming request. Viewer Response expects a valid HTTP response object, whereas URI rewrites must occur before origin selection.
Resolution
Attach URL rewrite logic to the Viewer Request event and return the modified request object.
Lesson 4: Relative Asset Resolution Depends on Trailing Slashes
Issue
The documentation rendered correctly at:
/iplusflow/
but loaded without CSS or JavaScript at:
/iplusflow
Root Cause
Browsers resolve relative asset paths based on the current URL.
Given:
<link rel="stylesheet" href="styles.css">
the browser resolves:
| Request URL | Asset Resolution |
|---|---|
/iplusflow/ |
/iplusflow/styles.css ✅ |
/iplusflow |
/styles.css ❌ |
Without a trailing slash, the browser interprets the final path segment as a file rather than a directory.
Resolution
Redirect directory requests to their trailing-slash equivalent before serving content.
Final CloudFront Function
The following CloudFront Function provides consistent directory routing by:
- Redirecting directory requests without a trailing slash.
- Rewriting directory requests to
index.htmlbefore forwarding them to the S3 origin.
function handler(event) {
var request = event.request;
var uri = request.uri;
if (!uri.includes('.') && !uri.endsWith('/')) {
return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
location: {
value: uri + '/'
}
}
};
}
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
return request;
}
Key Takeaways
- Browser-side caching can make infrastructure debugging misleading.
- CloudFront's Default Root Object only applies to the distribution root.
- URL rewrites should be implemented on the Viewer Request event.
- Relative asset paths require consistent trailing-slash handling for subdirectory deployments.
- A small CloudFront Function can eliminate most routing issues when serving static sites from S3.
If you're interested in seeing this architecture in practice, the documentation for my Chrome extension is available here:
Top comments (0)