Skip to main content

Overview

CdnFactory creates AWS CloudFront distributions with type-safe configuration. It delivers global content with caching, HTTPS, and custom domain support.
CDN is configured automatically when using the Payload pattern. Use CdnFactory directly when breaking out of patterns or building custom architectures.
Every configuration requires an originType. Use "auto" to detect the origin from a connected Fjall resource, or set "s3", "alb", or "http" explicitly.

Basic Usage

Configuration

Origin Types

Full Configuration Example

The /api/* behaviour sets allowedMethods: "ALL". Behaviours default to GET and HEAD, so an API path without it rejects every POST, PUT, and DELETE at the edge with a 405.

Parameters

Core Parameters

The origin, originHostname, and originRecord fields exist only on the "auto" variant. The "s3", "alb", and "http" variants take bucket, loadBalancer, and domainName respectively (see Origin-Specific Parameters). domainConfig is mutually exclusive with the literal domainNames/certificate/certificateArn surface. Pick one lane per distribution. It also requires the factory path (app.addCdn(CdnFactory.build(...))), which supplies the App used for zone lookup and certificate placement.

Behaviour Parameters

Behaviours vary caching, allowed methods, or the origin per path pattern. CloudFront applies viewer protocol policy, origin request policy, and compression automatically. Knob inheritance depends on whether the behaviour names its own origin:
  • Origin omitted. The behaviour is the default behaviour varied per path, so an omitted cachePolicy or allowedMethods inherits the distribution’s cachePolicy and defaultAllowedMethods.
  • Origin set. The behaviour is an independent config, so an omitted cachePolicy falls back to CACHING_OPTIMIZED and an omitted allowedMethods falls back to GET_HEAD.

Origin-Specific Parameters

When using explicit origin types, additional parameters are available.

S3 Origin (originType: "s3")

Both originAccess modes keep the bucket private. "oai" is the default because an Origin Access Identity avoids the bucket-policy cycle CloudFormation reports when the bucket and the distribution live in different stacks. Set originAccess: "oac" for Origin Access Control when they share one stack.

ALB Origin (originType: "alb")

Passing a compute resource resolves its load balancer directly:
Only ECS compute with a load balancer is accepted here. Lambda and EC2 compute types are refused at synth.

HTTP Origin (originType: "http")

Additional Configuration

logging takes over from the tier: an object switches logging on at any tier, false switches it off at any tier. Access logs are delivered through CloudWatch Logs (CloudFront standard logging v2) to bucket (the stack’s access-log bucket by default) under prefix (cloudfront/<id> by default) in format (json by default, or parquet, plain, w3c or raw, where parquet adds CloudWatch’s conversion charge). Objects land at <prefix>/<yyyy>/<MM>/<dd>/<HH>/…. Delivery works into any region, opt-in regions included, so the fjall 34 refusal is gone. Where logging resolves to on, CloudWatch Logs creates the delivery resources in us-east-1 only: an application deployed to us-east-1 keeps them in its CDN stack, and an application in any other region gets a <App>UsEast1CdnLogging stack that deploys after the CDN stack and is destroyed before it. A CDN with logging: false, or below enterprise with logging absent, creates neither the delivery resources nor that stack. That stack reads the distribution and the bucket through CDK cross-region references, so the application needs a concrete account and region at synth. The Fjall CLI supplies them. A bare cdk synth needs CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION exported. A bucket Fjall builds gets the bucket-policy statement the delivery writes through, and a key-policy statement when it is KMS-encrypted. An imported bucket needs you to add the statement the synth warning names, or delivery fails silently. Cross-account delivery is not supported, so the bucket must be in the same AWS account as the distribution. The pre-38 enableLogging, logBucket and logFilePrefix are refused at synth by name. See the changelog for the migration. The stack’s access-log bucket is created on first use, so logging: {} on a standard or resilient application adds one S3 bucket to the CDN stack: SSE-S3, unversioned, with a lifecycle rule that expires objects after 90 days. An enterprise CDN already has one, because the tier turns logging on. The bucket is per stack, so an enterprise application’s load balancer access logs go to a bucket of the same shape in the compute stack, under alb/<name>/. prefix must be a literal string at synth, because it is part of the delivery destination’s ARN and keys the destination’s name. A CDK token is refused. Leading and trailing slashes are dropped, and a prefix that is empty after that is refused rather than silently selecting the bucket-root AWSLogs/… layout. bucket, prefix and format are fixed at creation. Each keys the delivery destination’s name (fjall-cf-<distributionId>-<digest>), so changing any one of them replaces the destination under a new name. CloudFormation creates the replacement before deleting the original, and an identical name would collide. The bucket enters that digest as its literal name when imported, and as its construct path when Fjall builds it in the same application, because its physical name is a token at synth. So the one case that does not work in a single deploy is replacing the log bucket under an unchanged construct id: the digest does not move, the new bucket ARN makes CloudFormation replace the destination anyway, and the replacement collides on the name it kept. Set logging: false, deploy, then bring the new bucket in. AWS documents that a change to a distribution’s logging takes effect within twelve hours, and that reliable delivery begins about four hours after the delivery is created. Expect an empty prefix for the first few hours after a deploy, and a gap between the last object legacy logging wrote and the first v2 object. Objects legacy logging already delivered stay where they are and stay readable, and new objects nest beneath the same prefix. forwardHostHeader defaults to false, except for an ECS compute origin serving a custom viewer domain (domainNames or domainConfig set). The origin hostname differs from the viewer-facing name there, so the viewer host is forwarded via x-forwarded-host by default. An explicit forwardHostHeader: false always wins.

Framework-Managed Behaviour Settings

Fjall sets these per-behaviour values internally. They are not configurable through the factory.

Cache Policies

Passing a CDK ICachePolicy construct instead of a preset name works anywhere a preset does.

Common Patterns

Static Website with S3

Leave the bucket private. CloudFront reads it through the Origin Access Identity, so S3 website hosting and public read access are not needed.

Lambda Function URL (Auto-Detection)

With originType: "auto", the factory detects the Lambda function URL from the compute resource. A Lambda compute without functionUrl is refused at synth.

Multi-Origin Setup (OpenNext)

ECS Services

Pass the ECS compute itself as the origin. The CDN resolves a TLS-valid origin hostname from the service’s own routing configuration, never the raw load-balancer hostname, which no ACM certificate covers.
  1. When the compute declares exactly one routing host (the cluster domain), the CDN uses it as the origin hostname. The compute keeps owning that DNS record.
  2. Otherwise the CDN derives origin.<domain> inside the cluster’s zone and mints the Route 53 alias record itself, created before the distribution that depends on it.
Certificate coverage and listener forwarding for the chosen hostname are validated at synth time, so a hostname the listener would 404, or one the load balancer’s certificate does not cover, fails the plan instead of going live broken.
A cluster domain needs a hosted-zone source. fjall deploy injects the zone and certificate for a Fjall-managed domain, so the example above works as written. For a zone Fjall does not manage, name it on the cluster with domainConfig.hostedZone or domainConfig.managedDomain. A bare domain with no zone source throws at synth: Fjall never creates a hosted zone inside an application stack.
Here viewers hit example.com on the distribution while CloudFront fetches from api.example.com over HTTPS with a valid certificate, and the viewer host is forwarded via x-forwarded-host (see forwardHostHeader above). Use originHostname to override the resolution with a literal hostname: either a compute-declared routing host (the compute keeps owning its record) or a free name inside the cluster’s zone (the CDN mints the record for it). Redirect-only hosts are refused, because they 301 and never serve. Set originRecord: "none" when the derived hostname’s DNS record is managed elsewhere, for example during a migration. It is refused for routing hosts, whose records always belong to the compute. The overrides bind per compute. Distribution-level originHostname and originRecord configure the default origin’s ECS compute, and a behaviour whose origin is an ECS compute takes the same two fields on the behaviour entry itself. Two entries naming the same compute must agree, because there is one origin resolution (and at most one alias record) per compute. Setting them on a behaviour whose origin is not an ECS compute is refused. The explicit originType: "alb" lane remains available for a bring-your-own load balancer. For Fjall ECS computes prefer originType: "auto", because the alb lane targets the ELB hostname directly and leaves origin TLS to you. A cluster with no domain has no TLS-valid origin hostname at all, so the auto lane refuses it at synth. For that shape fjall add cdn generates originType: "alb" with protocolPolicy: "HTTP_ONLY", a plain-HTTP fetch straight to the cluster’s load balancer, with viewers still served over HTTPS by CloudFront. Give the cluster a domain later and regenerating the statement upgrades the CDN to the auto lane automatically.

Custom Domain Setup

Managed Domains

For a domain Fjall manages, set domainConfig and skip certificates entirely. The CDN resolves the hosted zone and a us-east-1 viewer certificate through the managed-domain binding, derives the alternate domain name, and owns the Route 53 alias record in its own stack. The record is created and removed with the distribution, so adding or deleting the application never leaves stray records in the zone.
The same lane is available from the CLI. fjall add cdn --app web --name AppCdn --defaultOriginRef Server --domain cdn.example.com generates exactly this configuration. Fjall never guesses a hosted zone from the domain’s labels. Bind a managed domain with fjall domain create or fjall domain import, or set zoneName explicitly.

Prerequisites (bring-your-own certificate)

  1. Domain registered and DNS managed (Route 53 recommended)
  2. ACM certificate in us-east-1 (required for CloudFront)

With Route 53

With External DNS

Create a CNAME record pointing to the CloudFront distribution domain:

Gating a Preview Distribution

accessGate puts HTTP basic auth in front of every path with a CloudFront Function. Requests without matching credentials get a 401 and a WWW-Authenticate challenge.
The credentials are base64-encoded into the CloudFront Function source, so they appear in the synthesised CloudFormation template. Use accessGate to keep previews and staging sites out of search results, not to protect sensitive data.
Set accessGate: false (or omit it) to leave the distribution open.

Accessing CDN Information

Security

Every distribution ships with these defaults: Security headers are opt-in on S3 origins through responseHeadersPolicy.
From the first fjall 38 deploy every distribution reads FAILED on Security Hub control CloudFront.5 (“CloudFront distributions should have logging enabled”), whether logging is on or off. The control reads the distribution’s legacy Logging block, and standard logging v2 does not render one. Where logging is on the logs are still being delivered, so read the finding as expected.

Best Practices

  1. Use originType: "auto" for most cases. Fjall picks the origin from the connected resource, and for ECS computes this includes resolving a TLS-valid origin hostname.
  2. Use domainConfig for Fjall-managed domains. The CDN owns its zone lookup, certificate, and DNS record, so there is nothing to wire by hand.
  3. Cache static assets with the CACHING_OPTIMIZED policy.
  4. Disable caching for APIs with CACHING_DISABLED, and set allowedMethods: "ALL" so writes reach the origin.
  5. Set defaultRootObject: "index.html" on S3-backed sites, otherwise a request for / returns nothing.
  6. Create bring-your-own certificates in us-east-1. CloudFront requires certificates in this region (managed domains handle this automatically).

Next Steps

Compute Factory

Deploy Lambda and ECS origins

Storage Factory

Configure S3 bucket origins

Static Site Pattern

Ship a static site behind CloudFront

Payload Pattern

Full-stack deployment with CDN