Visitors who type www.example.net should land on example.net. A CloudFront Function handles the 301 redirect at the edge with zero origin calls. This post extends the routing function from post 15, adds two Route 53 records, and updates the distribution’s aliases.
Why CloudFront Functions
Lambda@Edge can do redirects, but it is overkill for this. CloudFront Functions run in every edge location, execute in sub-millisecond time, have no cold starts, and cost about 1/6th the price. They are purpose-built for lightweight request/response transformations like redirects, header manipulation, and URL rewrites.
Lambda@Edge runs in regional edge caches (a subset of locations), has cold starts on the first invocation, and bills per GB-second. For a simple host check and 301, CloudFront Functions are the right tool.
Certificate
The ACM certificate from post 3 already covers *.example.net via the wildcard SAN. The wildcard matches www.example.net, so no certificate changes are needed.
One Function per Event Type
CloudFront allows exactly one function per event type on a cache behavior. The React template already attaches SPARoutingFunction at viewer-request (post 15), and the Astro template attaches SubdirectoryIndexFunction (post 14).
A second viewer-request association is not possible, and a standalone redirect function would replace your routing function instead of running alongside it. The fix is to add the redirect check to the top of the function you already have.
The plain static template from post 13 has no viewer-request function yet. There you can define the redirect as its own
AWS::CloudFront::Functionwith just thewww.check below, and attach it with a newFunctionAssociationsentry.
The Merged Function
The redirect check goes first, before the routing logic. Here is the updated SPARoutingFunction from the React template.
SPARoutingFunction:
Type: AWS::CloudFront::Function
Properties:
Name: !Sub '${ProjectName}SPARouting'
AutoPublish: true
FunctionConfig:
Comment: Redirects www to apex, rewrites non-asset paths to /index.html
Runtime: cloudfront-js-2.0
FunctionCode: |
function handler(event) {
var request = event.request;
var host = request.headers.host.value;
if (host.startsWith('www.')) {
return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
location: { value: 'https://' + host.slice(4) + request.uri }
}
};
}
var uri = request.uri;
var isAsset = /\.(html|js|css|png|jpg|jpeg|gif|ico|svg|json|webp|woff|woff2|ttf|eot|map|txt|xml|mp4)$/i.test(uri);
if (isAsset) {
return request;
}
request.uri = '/index.html';
return request;
}
The function checks the Host header first. If it starts with www., the function returns a 301 Moved Permanently response with the Location header pointing to the same path on the apex domain. The host.slice(4) strips the www. prefix.
Returning a response object short-circuits CloudFront. No cache lookup, no origin fetch, no data transfer charges. The redirect happens entirely at the edge.
Apex requests fall through to the SPA routing logic from post 15, unchanged.
Runtime: cloudfront-js-2.0 is the latest CloudFront Functions runtime. It supports ES 6+ syntax and the crypto module. The older cloudfront-js-1.0 runtime works too, but there is no reason to use it for new functions.
AutoPublish: true promotes the function to the LIVE stage on every deploy. Without this, CloudFormation creates a DEVELOPMENT stage function that is not attached to any distribution.
For the Astro template, add the same
if (host.startsWith('www.'))block to the top of theSubdirectoryIndexFunctionfrom post 14.
Distribution Update
One change to the CloudFront distribution from post 8: add www.example.net to the Aliases array.
CloudFrontDistribution:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
Aliases:
- !Ref DomainName
- !Sub 'www.${DomainName}'
Aliases now includes both the apex and the www subdomain. CloudFront requires an alias entry for every domain it serves. Without the www alias, CloudFront rejects requests for www.example.net with a 403 before your function ever runs.
The FunctionAssociations on the DefaultCacheBehavior stays exactly as it was in post 15. The function is already attached at viewer-request; only its code changed.
Route 53 Records
Two new alias records point www.example.net to the same CloudFront distribution. One for IPv4, one for IPv6. Same pattern as the apex records in post 12.
WwwDomainRecord:
Type: 'AWS::Route53::RecordSet'
Properties:
HostedZoneId: !Ref HostedZoneId
Name: !Sub 'www.${DomainName}'
Type: A
AliasTarget:
DNSName: !GetAtt CloudFrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
WwwDomainRecordAAAA:
Type: 'AWS::Route53::RecordSet'
Properties:
HostedZoneId: !Ref HostedZoneId
Name: !Sub 'www.${DomainName}'
Type: AAAA
AliasTarget:
DNSName: !GetAtt CloudFrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
Name: !Sub 'www.${DomainName}' creates records for the www subdomain. Route 53 resolves www.example.net to the distribution’s edge IPs, the request hits CloudFront, and the function redirects to the apex.
HostedZoneId: Z2FDTNDATAQYW2 is the fixed AWS constant for CloudFront, same as post 12.
Git Sync Role
The CloudFront Function permissions (CreateFunction, DescribeFunction, GetFunction, UpdateFunction, DeleteFunction, PublishFunction) are already in the Git Sync role from post 8. The Route 53 permissions are in the role from post 4. No role changes needed for this post.