Cache invalidation feels like a universal repair button. It is also slow, expensive at scale, and often hides a missing versioning strategy. The durable solution is to make different classes of files cache differently.
Separate HTML from immutable assets
HTML, manifests, and robots files need short revalidation windows. Hashed JavaScript, CSS, fonts, and images can use long immutable caching because a new filename represents a new version.
The deployment symptom
A deployment reported success in S3, but users continued to receive an older HTML document. Operators responded with a full CloudFront invalidation. It worked eventually, but the deployment took longer, generated unnecessary edge work, and taught everybody the wrong lesson: purge first, investigate later.
The actual problem was inconsistent cache policy. HTML and hashed assets were being treated alike, so a new application shell could remain stale while the asset names suggested the deployment was complete.
aws s3 sync out/ s3://portfolio \
--delete \
--cache-control "public,max-age=31536000,immutable" \
--exclude "*.html" \
--exclude "sitemap*.xml" \
--exclude "robots.txt"
aws s3 sync out/ s3://portfolio \
--delete \
--cache-control "public,max-age=0,must-revalidate" \
--exclude "*" \
--include "*.html" \
--include "sitemap*.xml" \
--include "robots.txt"Invalidate the smallest necessary path
When a purge is required, target the changed HTML or manifest paths. A full wildcard invalidation should be an exception reserved for a broken deployment or a security response, not the default after every build.
Test from the edge
Verify cache-control headers, Age, ETag, and content from more than one region. A successful upload to S3 is not proof that CloudFront is serving the intended version.
The resolution
We gave HTML short revalidation, gave fingerprinted assets immutable caching, and invalidated only the changed entry documents when necessary. The deployment check now requests the public hostname, records headers, and compares a content marker before declaring success.
The remaining trade-off is that a broken HTML document can still spread until revalidation completes. That is why the deployment keeps a narrow emergency invalidation path, while normal releases rely on versioned assets and correct headers.
Treat cache policy as part of the release contract
Only fingerprinted assets deserve a year-long immutable lifetime. A public image at a stable URL is not immutable just because it is an image; version it or give it a revalidation policy. In AWS CLI filters, include rules do not narrow the default include-all set by themselves, so exclude everything first in the short-cache sync and then include only the intended document paths. Verify the response headers at the public hostname after deployment, not just the metadata on the S3 object.
A purge is the cache equivalent of an undo button: useful when needed, but not a release strategy.
The boring deployment is the successful one
Good caching makes the correct deployment boring: new assets get new names, HTML revalidates, and operators rarely need to purge anything.