We are using Symfony (Sulu) with Varnish as reverse proxy, with tag based invalidation (xkey) which works as expected. Now we want to use ESI for dynamic blocks on our website, separate from the "main" cache lifetime.
But each time the webpage is cached in full, even after hard refresh/clear browser cache or using new browser, the page is always displaying the first cache entry.
We have enabled ESI in Symfony:
framework:
...
esi: true
fragments: { path: /_fragment }
And provide render_esi in our Twig templates:
navbar.html.twig
...
{{ render_esi(controller('App\Controller\Website\LoginButtonController::renderEsiLoginButton')) }}
<div class="navbar-main__toggler"></div>
</div>
LoginButtonController
class LoginButtonController extends AbstractController
{
public function renderEsiLoginButton(): Response
{
$response = $this->render('esi/_login_button.html.twig', ['random_nr' => rand()]);
$response->setMaxAge(10);
$response->setSharedMaxAge(10);
$response->setPublic();
return $response;
}
}
_login_button.html.twig
<div class="navbar-main__action-account">
<div class="account__dropdown">
<a href="#" class="account__user">{{ random_nr }}</a>
...
</div>
</div>
And our Varnish default.vcl:
sub vcl_recv {
call fos_tags_xkey_recv;
call fos_user_context_recv;
call sulu_recv;
// # Add a Surrogate-Capability header to announce ESI
set req.http.Surrogate-Capability = "abc=ESI/1.0";
...
sub vcl_backend_response {
set beresp.grace = 2m;
call fos_user_context_backend_response;
call sulu_backend_response;
// Check for ESI acknowledgement and remove Surrogate-Control header
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}
}
...
Our fos_http_cache config:
fos_http_cache:
tags:
enabled: true
response_header: xkey
max_header_value_length: 1024
proxy_client:
symfony:
use_kernel_dispatcher: true
user_context:
enabled: true
role_provider: true
# user_hash_header: 'X-User-Context-Hash'
hash_cache_ttl: 900
and sulu http cache:
when@prod: &prod
sulu_http_cache:
debug:
enabled: false
tags:
enabled: true
cache:
max_age: 240
shared_max_age: 480
proxy_client:
symfony:
enabled: false
varnish:
enabled: true
servers: [ '%env(VARNISH_SERVER)%' ]
tag_mode: purgekeys
When using curl, we also see the esi:include tags correctly:
<span class="navbar-main__action-search-button"></span>
<esi:include src="/_fragment?_hash=HAz3Ym5wXab4GXTRKnq8oUR3WwnGqUpprL0niBc%2BEwg%3D&_path=_format%3Dhtml%26_locale%3Den%26_controller%3DApp%255CController%255CWebsite%255CLoginButtonController%253A%253ArenderEsiLoginButton" onerror="continue" />
<div class="navbar-main__toggler"></div>
But the webpage is still cached in full, even after hard refresh/clear browser cache or using new browser, the page is always displaying the first random number. Until we clear the Varnish cache in full.
What are we missing?
2
Answers
By the looks of it, you’re doing everything right. But some targeted
varnishlog
commands will give us a better understanding of how Varnish is caching the various fragments.I’m going to need you to send me the output of the following command:
My assumption is that the issue is occurring on the homepage. If that’s not the case, alter the filter in the
varnishlog
command.When Varnish performs the various fetches, we can have a look at the
TTL
tags in the logs. This will tell us how Varnish decided on the TTL of each fragment (either through a TTL set in the VCL code, or through theCache-Control
header.The logs will also return the various ESI subrequests as separate log transactions, making it easier to understand what happened when.
Please put the log output in your original question and I’ll help you figure out what’s going on.
Only thing that comes directly to my eye comparing this to the symfony ESI-Docs
is that
you have a relative link in the src attribute of the esi tag, you could try using the
absolute_uri
option of render_esi (IMHO it does not really make sense though that this defaults to false)But yeah, i think its worth a try