Hugo Pretty URLs CloudFront Fix
Resolving Directory Index Issues for Static Sites
Problem Statement
Hugo static sites generate clean URLs by creating directory structures with index.html
files. However, AWS CloudFront and S3 don’t automatically serve index.html
files when users request directory paths, resulting in “Access Denied” errors.
Issue Example:
- Hugo generates:
/blog/my-post/index.html
- User requests:
https://example.com/blog/my-post/
- Result: Access Denied error
Root Cause Analysis
Hugo URL Structure:
Hugo with uglyURLs: false
creates:
/blog/index.html
/blog/post-title/index.html
/categories/tech/index.html
CloudFront Behavior:
- Requests ending in
/
look for directory listings - S3 REST API endpoints don’t serve directory index files
- CloudFront passes requests as-is to S3 without index file resolution
Solution: CloudFront Function
Implementation:
A CloudFront Function that intercepts viewer requests and rewrites URLs to append index.html
when needed.
Function Code:
function handler(event) {
var request = event.request;
var uri = request.uri;
// Check whether the URI is missing a file name
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// Check whether the URI is missing a file extension
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
How It Works
Request Processing:
- User visits
https://example.com/blog/
- CloudFront Function intercepts the request
- Function detects URL ends with
/
- Function rewrites request to
/blog/index.html
- S3 serves the actual file
- User sees content at clean URL
URL Patterns Handled:
/blog/
→/blog/index.html
/blog/my-post
→/blog/my-post/index.html
/categories/tech/
→/categories/tech/index.html
- Static assets (
.css
,.js
,.png
) remain unchanged
Implementation Steps
Step 1: Create CloudFront Function
# Create function file
cat > index-rewrite.js << 'EOF'
function handler(event) {
var request = event.request;
var uri = request.uri;
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
EOF
# Create CloudFront function via AWS CLI
aws cloudfront create-function \
--name "hugo-index-rewrite" \
--function-config Comment="Append index.html for Hugo pretty URLs",Runtime="cloudfront-js-1.0" \
--function-code fileb://index-rewrite.js
Step 2: Publish Function
# Get function ETag
ETAG=$(aws cloudfront describe-function --name "hugo-index-rewrite" --query 'ETag' --output text)
# Publish function
aws cloudfront publish-function \
--name "hugo-index-rewrite" \
--if-match $ETAG
Step 3: Associate with Distribution
- Open CloudFront console
- Select your distribution
- Edit Default Cache Behavior
- Scroll to Function Associations
- Select Viewer Request event type
- Choose your function
- Save changes
Benefits
User Experience:
- Clean, professional URLs maintained
- No broken links or 404 errors
- Consistent navigation experience
- SEO-friendly URL structure
Technical Advantages:
- Minimal latency impact (edge execution)
- No backend infrastructure required
- Works with existing Hugo configuration
- Compatible with all Hugo themes
Performance:
- Function executes at CloudFront edge locations
- No additional round trips to origin
- Cached responses benefit from CDN
- Sub-millisecond execution time
Alternative Solutions Compared
Option 1: S3 Website Hosting
- Pros: Built-in index document support
- Cons: Limited HTTPS/SSL options, no advanced CloudFront features
Option 2: Lambda@Edge
- Pros: More powerful processing capabilities
- Cons: Higher latency, more complex, higher cost
Option 3: CloudFront Function (Recommended)
- Pros: Fast execution, simple logic, cost-effective
- Cons: Limited to basic request/response manipulation
Monitoring & Validation
Testing Commands:
# Test directory requests
curl -I https://example.com/blog/
curl -I https://example.com/categories/tech/
# Test clean URLs
curl -I https://example.com/blog/my-post
curl -I https://example.com/about
# Verify static assets unchanged
curl -I https://example.com/css/style.css
curl -I https://example.com/js/main.js
Expected Results:
- All directory and clean URLs return HTTP 200
- Content-Type headers show
text/html
for pages - Static assets serve normally
- Browser URL bar shows clean URLs
Cost Impact
CloudFront Function Pricing:
- $0.10 per 1 million function invocations
- Typical website: < $1/month additional cost
- No additional infrastructure required
- Scales automatically with traffic
Status: Production Ready
Execution Location: CloudFront Edge
Latency Impact: < 1ms
Compatibility: All Hugo versions with uglyURLs: false
This solution provides a robust, scalable fix for Hugo pretty URL issues on CloudFront while maintaining optimal performance and minimal cost impact.