This discusses an issue in Magento when Varnish is active, that on certain pages (mostly containing /customer in the url) you'll see the top navigation missing and an esi:include tag being included in the html. This is related to Varnish not understanding certain compression algorithms, like Brotli.
Note, we're only talking about GET requests here, not about POST requests.
-
Magento has a way to say if a page is cacheable or not. A page is cacheable by default, unless it mentions
cacheable="false"in one of its blocks, in its layout xml file. -
Based on if a page is cacheable or not, Magento will send different caching HTTP headers back
-
Just for information, following pages are considered cacheable (unless the old skool captcha module is active, which makes most of these uncacheable):
customer/account/confirmationcustomer/account/createcustomer/account/forgotpasswordcustomer/account/logincustomer/account/logoutSuccess
-
And following pages are considered uncacheable:
customer/account/createpasswordcustomer/account/editcustomer/accountcustomer/account/indexcustomer/address/formcustomer/address/index
-
Magento has a feature that if Varnish is enabled and a page is considered cacheable, it will output an
esi:includetag for the top navigation block, as it's more efficient to re-use that block instead of the backend having to build and return it for each page. -
Magento's default VCL file for Varnish contains this section in the
vcl_recvblock:# Bypass customer, shopping cart, checkout if (req.url ~ "/customer" || req.url ~ "/checkout") { return (pass); }
This basically says, if
/customeror/checkoutis part of the url, don't fetch the content from the cache, just send the request to the backend each time, ignore what Magento's cache headers say and don't store its results in cache. -
In Varnish, when executing
return (pass)in thevcl_recvsection, it will skip changing encoding headers (like brotli) when passing the request to the backend:Unless returning from vcl_recv with pipe or pass, Varnish modifies req.http.Accept-Encoding: if the client supports gzip req.http.Accept-Encoding is set to "gzip", otherwise the header is removed.
And it will thus potentially receive compressed data back it doesn't understand. That's fine as long as this data doesn't contain an
esi:includetag.
So in specific cases, if for example the browser supports brotli compression and Varnish does not, when executing areturn (pass), Varnish will pass the request to the backend and the backend can then return a brotli compressed result. Varnish can't understand that response, and it will return the response to the client as-is. So, if such a blob of compressed data contains anesi:includetag, Varnish won't be able to see that and will thus return it as-is to the client without resolving the ESI tag first. -
Note that Varnish has support for brotli compression, but only in its Enterprise solution, not in the free Community one.
-
In following cases a
return (pass)is triggered from the VCL in thevcl_recvsection and thus in these potential cases Varnish can receive potentially compressed data it can't read:- When a request is not a
GETorHEADrequest => this is fine, such requests are not cacheable and should never contain anesi:includetag from Magento - When calling the
pub/health_check.phpfile, this is fine, it's not going to contain anesi:includetag - When the url starts with
/mediaor/static, this is fine, those are static assets (js, css, images, ...) - When the url contains
/graphqland is authenticated, this is fine, those won't contain anesi:includetag - When the url contains
/customeror/checkout. This is the problematic section, as some of the urls containing/customerwill be cacheable and will be able to contain anesi:includetag
- When a request is not a
-
I believe that forcing from the VCL file that all pages containing
/customerin the url to be uncacheable from Varnish is wrong, this should not be hardcoded and Varnish should follow the instructions the HTTP cache headers tell it, if a page is cacheable or not. Removing this will avoid thereturn (pass)and will not run into problems with brotli compression.
So I would suggest we change:# Bypass customer, shopping cart, checkout if (req.url ~ "/customer" || req.url ~ "/checkout") { return (pass); }
into:
# Bypass shopping cart, checkout if (req.url ~ "/checkout") { return (pass); }
This
/customerexception was added here, but it doesn't contain much explanation why it was added, so I believe this shouldn't have been accepted. -
An alternative solution is to disable brotli compression in the backend webserver, but I think the prefered solution is to do it through the VCL file itself.