DEV Community

Vivek Vohra
Vivek Vohra

Posted on

AWS S3 + CloudFront Subdirectory Hosting: Architecture & Edge Cases

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:

  • /iplusflow403 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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

but loaded without CSS or JavaScript at:

/iplusflow
Enter fullscreen mode Exit fullscreen mode

Root Cause

Browsers resolve relative asset paths based on the current URL.

Given:

<link rel="stylesheet" href="styles.css">
Enter fullscreen mode Exit fullscreen mode

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:

  1. Redirecting directory requests without a trailing slash.
  2. Rewriting directory requests to index.html before 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;
}
Enter fullscreen mode Exit fullscreen mode

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)