Introduction
I noticed that the deployed site was still returning an outdated llms.txt after the path had been migrated from a static file to dynamic generation. CloudFront was serving the legacy static object from the S3 _assets bucket. The application change had shipped, but the response at the public path hadn't changed.
I removed the stale _assets/llms.txt object from S3, then invalidated the CloudFront cache for that path. Fresh requests then received the updated, dynamically generated llms.txt. That is the supported before-and-after result. It does not independently establish where the old asset was selected or trace which backend produced each response.
Clearing a cached response is different from removing a stale source that can still serve the path. This fix addressed both. The sequence worked; the routing mechanism and the effect of invalidation alone remain separate questions.
The Rule That Shipped
The rule I applied was specific: delete the stale S3 object at _assets/llms.txt first, then invalidate the CloudFront cache for the llms.txt request path. This was a targeted cleanup of one legacy asset, not a general change to CloudFront routing.
The old object itself—not cache freshness—was the authoritative source being returned. Invalidation alone, without removing that object, would not have changed what was served. An invalidation removes a cached copy; it does not remove the underlying S3 object. If request handling continued to select that object, a cache miss could retrieve the same stale content again.
That explanation is not an independently tested counterfactual. I did not separately test a second invalidation while leaving the S3 object in place. The evidence supports deletion followed by invalidation as the successful sequence, not a controlled comparison proving that no other sequence could work.
The rule also does not mean CloudFront automatically switches to a dynamic route whenever an S3 object disappears. Some routing or application behavior selected the legacy asset here, but the evidence does not identify where that selection happened.
Worked Example
The input was a request for llms.txt after the dynamic route had been deployed but before the legacy S3 object was removed. The request path was unchanged, yet CloudFront kept returning the old static file from the _assets bucket. Deploying dynamic generation had not been enough to change the response at that path.
I deleted the stale _assets/llms.txt object from S3, then invalidated the CloudFront cache for the corresponding request path. After that sequence, fresh requests to the same path returned the updated, dynamically generated content instead of the legacy file.
Two observations support the immediate repair: the identified stale object had been removed, and fresh requests returned the updated content. Neither observation supplies a routing trace, establishes how the request reached its backend, or shows that removing an object universally causes a dynamic route to take over. Keeping the request path constant gives this case a clear before-and-after comparison without proving an undocumented routing mechanism.
Challenges and Tradeoffs
The main tradeoff was between fixing a known conflict and establishing a broader migration process. Removing the identified object and invalidating its path addressed the observed failure with a narrow scope. It did not establish whether other assets had similar conflicts or explain why this legacy object remained authoritative for the request.
Invalidation alone was plausible because the visible symptom was stale content. But cache eviction and source selection are different operations. If request handling still selects the old object, clearing an edge copy leaves the underlying problem intact. That was the reasoning behind removing the object, although the invalidation-only alternative was not independently tested.
One possible migration practice is to identify legacy objects associated with a path and explicitly decide whether to retain or remove them when introducing dynamic generation. Verification would then need to check that the intended route remains reachable after cleanup. Deletion is not a routing configuration in itself.
Versioned static-asset keys are another possible design choice, but changing a key alone does not establish how the original llms.txt path resolves. Neither approach was demonstrated as a preventive measure in this incident. Their usefulness would depend on the actual request-handling behavior, which remains unverified here.
Verification Process
The completed verification was at the response level: after deleting the stale object and invalidating the path, I observed fresh requests returning the updated llms.txt. That confirms the reported outcome for those requests. It does not establish immediate propagation to every edge location, and no broader propagation result is available.
That observation is separate from independent backend attribution. The supported outcome is the updated, dynamically generated file, but the evidence contains no request trace separately proving which backend generated an individual response. Likewise, the evidence identifies the legacy S3 object as the stale content being served without establishing the routing or application decision that selected it.
Three limits remain. First, I have not verified the specific CloudFront configuration detail—such as origin priority or a behavior path pattern—that caused the static S3 origin to take precedence over the dynamic route. Second, whether a second invalidation without deleting the object would have had any effect was reasoned about, not independently tested. Third, whether other static files in the _assets bucket have similar unnoticed conflicts with routes since migrated to dynamic generation is unchecked.
Those questions need separate verification. A routing trace would investigate source selection; a controlled invalidation-only test would investigate the counterfactual; an asset-and-route audit would investigate the scope of possible conflicts. None of that work should be reported as completed.
Conclusion
The immediate repair was to delete the stale _assets/llms.txt object, then invalidate the CloudFront cache for the request path. Fresh requests returned the updated, dynamically generated llms.txt rather than the old static file.
I can report that successful sequence without claiming a complete routing diagnosis. When a static-to-dynamic migration appears not to take effect, both cached responses and legacy source objects need attention. Here, the cleanup resolved the observed response problem. The selection mechanism, the invalidation-only outcome, and the possibility of similar conflicts elsewhere remain unverified.