Introduction
When building a site on AWS CloudFront, a common requirement arises: "I want to route only the root (/) to a specific origin, or apply unique logic strictly to that path."
The challenge is that CloudFront’s path patterns lack a way to match exactly /. Typically, you end up relying on the Default Behavior (*), which captures everything from scripts to styles. This makes it difficult to separate caching strategies or route requests to different origins cleanly.
This article explores a clean way to solve this by taking advantage of the specific behavior of the ? wildcard: using the /?* pattern to isolate the root path.
The Constraint: CloudFront Path Patterns
CloudFront’s routing relies on path patterns with specific limitations:
- No regular expressions
-
Two available wildcards:
-
*: Matches any string of 0 or more characters. -
?: Matches exactly one character.
-
- Priority-based evaluation: The first pattern to match a request wins.
Within these constraints, the goal is to find a way to express “everything except the root” so the root itself can be handled independently.
How the Pattern Works
The key lies in the strict "exactly one character" rule of the ? wildcard.
Breaking Down the Pattern
The /?* pattern works as follows:
-
/— A literal slash. -
?— Exactly one character (required). -
*— Any string of 0 or more characters.
This means that /?* matches any path that has at least one character following the initial slash.
💡 The Mechanism
By assigning
/?*a higher priority, you ensure that only the root request (/)—which has zero characters after the slash—does not match and falls through to the Default Behavior (*).
Matching Results
| Path | Matches /?*? |
Reason |
|---|---|---|
/index.html |
✅ Yes | Matches / + i + ndex.html
|
/main.js |
✅ Yes | Matches / + m + ain.js
|
/assets/style.css |
✅ Yes | At least one character follows the slash |
/ |
❌ No | Zero characters follow the slash |
Fine-Grained Routing: SSR vs. Static Assets
The most practical application is a hybrid setup (e.g., Astro, Next.js), where you need to separate dynamic rendering from static delivery.
-
/(Root): Route to a dynamic origin (Lambda, App Runner, etc.) to serve dynamic content and generate SEO metadata. -
Everything else (
/assets/*,/favicon.ico, etc.): Serve directly from S3 for maximum performance and lower costs.
Without this pattern, you’d need to manually enumerate every file extension. With /?*, you get a robust, catch-all solution for assets.
CDK Implementation Example
This approach keeps your infrastructure-as-code clean and easy to reason about:
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
const distribution = new cloudfront.Distribution(this, 'MyHybridDist', {
// 1. Default (*) Behavior — Lowest Priority
// Only the root (/) falls through to here.
defaultBehavior: {
origin: new origins.HttpOrigin('ssr-api.example.com'),
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED, // SSR requires fresh data
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
additionalBehaviors: {
// 2. Everything except root (/?*) — Higher Priority
// Serve and cache all static assets from S3.
'/?*': {
origin: origins.S3BucketOrigin.withOriginAccessIdentity(assetBucket),
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
},
});
Strategic Advantages
-
Zero-Maintenance Asset Routing: No need to update your config when adding new file types (
.webp,.avif, etc.). - Architectural Clarity: The logic for your "Entry Point" and your "Assets" is split cleanly at the edge.
-
Optimized Caching: Apply a
no-cachepolicy to your dynamic root while keeping aggressive, long-term caching for everything else.
⚠️ A Subtle but Crucial Limitation
When combining this with CloudFront Functions, keep in mind:
Rewriting request.uri within a Function does not trigger a re-evaluation of cache behaviors.
If a request hits the Default Behavior (*) and your Function rewrites the URI to /index.html, CloudFront will not move that request over to the /?* behavior. Execution stays within the behavior that matched the original request.
Summary
The ? wildcard in CloudFront is often overlooked, but it can be surprisingly powerful. Its strictness allows for an elegant implementation of path-based negation.
Whether you are managing SSR setups, multilingual redirects, or maintenance pages, /?* is a reliable tool for handling the root path as a first-class citizen in your routing logic.
This article was originally published in Japanese on archelon-inc.jp.
Top comments (0)