I ran into the same problem. At first I investigated writing a Lambda@Edge function that would check to see if content should be invalidated and then break the cache by appending a query parameter to the URL. But this seemed to be just as problematic (and expensive) as invalidating the entire S3 bucket. The solution I settled on instead was using the Cache-Control: no-cache header on the specific assets that shouldn't be cached by CloudFront and then just invalidating the other cached assets with query parameters. And this method doesn't require issuing any CloudFront invalidations.
So for example, if I'm hosting a static site on CloudFront / S3 that looks like this:
- index.html
- header.jpg
- site.css
I upload it to S3 as follows:
- index.html // Cache-Control: no-cache
- header.jpg // Linked with <img src='header.jpg?uniquedigest' />
- site.css // Linked with <link href='site.css?uniquedigest' />
This way everything is kept up to date in CloudFrontL Only the index.html isn't cached and all the assets are still cached after the initial fetch from the origin S3 bucket. The one downside is that the index.html is always fetched from origin but HTML should be small enough that this doesn't have a big performance impact.