AWS
S3
Cloudfront
Static Website Hosting
index.html Configuration

How do I serve index.html in subfolders with S3/Cloudfront?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When you put a static site behind CloudFront, the root URL can be easy to handle, but subfolders are where people get surprised. Default Root Object only helps for /, not for paths such as /docs/ or /blog/, so serving index.html in subfolders usually requires a URL rewrite strategy.

Understand the S3 and CloudFront Difference

S3 static website hosting and CloudFront do not behave identically.

If you use the S3 website endpoint directly, S3 has website-style behaviors such as serving index documents for directory-style requests. Once CloudFront is in front, especially with an S3 REST origin plus origin access control, you should not assume those website semantics still solve every path automatically.

That is why /index.html works but /docs/ may return a 403 or 404 unless you rewrite the request to /docs/index.html.

Default Root Object Only Solves the Root

In CloudFront, this setting:

text
Default Root Object = index.html

helps only when the request URI is /.

It does not automatically map:

  • '/docs/ to /docs/index.html'
  • '/blog/ to /blog/index.html'

That distinction is the source of most confusion.

Rewrite Directory Requests with a CloudFront Function

The clean modern fix is a viewer-request rewrite. A CloudFront Function can append index.html to directory-style paths before the request goes to S3.

javascript
1function handler(event) {
2    var request = event.request;
3    var uri = request.uri;
4
5    if (uri.endsWith("/")) {
6        request.uri = uri + "index.html";
7    } else if (!uri.includes(".")) {
8        request.uri = uri + "/index.html";
9    }
10
11    return request;
12}

This handles:

  • '/ as /index.html'
  • '/docs/ as /docs/index.html'
  • '/docs as /docs/index.html'

while leaving asset requests such as /app.js or /images/logo.png alone.

That is usually the simplest answer for folder-based static sites.

SPA Fallback Is a Different Problem

Be careful not to mix two different requirements:

  1. serve /section/ from /section/index.html
  2. serve unknown app routes from one root index.html

The first is a directory-index problem. The second is a single-page application fallback problem.

For SPA fallback, the rewrite often becomes:

  • if the path has no file extension, send /index.html

That is useful for React Router and similar client-side routers, but it is not the same thing as preserving per-folder index.html files.

So define the routing model clearly before implementing a rewrite.

Keep the S3 Layout Consistent

If you want /docs/ to work, the objects should actually exist as:

text
docs/index.html

and not only as:

text
docs.html

The rewrite cannot invent a directory structure that is not in the bucket. It only changes which key CloudFront requests.

This also means your build pipeline should upload files in the exact folder structure your URLs imply.

Error Responses Are a Fallback, Not the Best Primary Mechanism

Some teams use custom error responses so that 403 or 404 errors return index.html. That can work for SPA fallback, but it is a poor primary solution for subfolder index handling because:

  • true missing files can be masked
  • debugging becomes harder
  • every miss turns into an error-based control flow

A request rewrite is usually cleaner and faster because CloudFront asks for the right object the first time.

Test the Important URLs Explicitly

After you add the rewrite, test:

  • '/'
  • '/docs/'
  • '/docs'
  • '/docs/index.html'
  • one real asset such as /docs/app.css

This matters because a rewrite that is too broad can accidentally rewrite asset URLs too, which breaks the site in a more confusing way than the original 404.

Common Pitfalls

  • Assuming Default Root Object applies to every subfolder path.
  • Using a root-level SPA fallback when the site actually has folder-specific index.html files.
  • Rewriting every extensionless path without checking whether that matches the site structure.
  • Forgetting that the requested S3 keys must still exist in the bucket.
  • Relying on custom error responses when a viewer-request rewrite would be cleaner.

Summary

  • CloudFront Default Root Object only covers the root path.
  • To serve index.html in subfolders, rewrite directory-style URLs to /folder/index.html.
  • A CloudFront Function is usually the cleanest way to do that.
  • SPA fallback and subfolder index handling are related but different routing problems.
  • Always verify that the S3 object layout matches the rewritten URLs.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.