Page Caching shows old view mode.

Created on 14 February 2024, 9 months ago
Updated 15 February 2024, 9 months ago

Problem/Motivation

I have a typical page/article content type, with the field to switch the view mode. And it works as expected when I have "Do not cache markup" enabled on the /admin/config/development/settings page.

However once caching is enabled, I can edit a page, updates its view mode. After saving the page I see the new view mode. However once visiting a few more page, and coming back to the original page it has reverted back to its default/full view mode.

So seems to be an issue with caching, and switching view modes.

Steps to reproduce

1. A node with the view mode field.
2. create a view mode called "no headers".
3. Copy the "page.html.twig" to a file called "page--article--no-headers.html.twig" and modify that so you can see if the page uses that viewmode template.
4. have Page caching enabled.
5. Switch a nodes view mode, view other unmodified nodes.
6. Revisit the switched node and see it reverted back to the original view mode.

Proposed resolution

I asked AI to help debug the problem it says:

It implements hook_entity_view_mode_alter(), checks if the entity is fieldable, and then uses the getViewModeToSwitchTo() method of the view_mode_switch service to determine if the view mode should be switched. If a new view mode is returned, it changes the view mode of the entity.

However, the issue you're experiencing might not be due to this code, but rather due to how Drupal's caching system works. When Drupal's caching is enabled, it stores the rendered output of pages and blocks. If the cache is not properly invalidated when the view mode changes, Drupal might serve the cached version of the page with the old view mode.

One way to address this issue is to invalidate the cache when the view mode changes. You can do this by adding a call to \Drupal::service('cache.render')->invalidateTags($entity->getCacheTags()); after the view mode is changed:

// Switch view mode (if applicable).
    if (($switch_to_view_mode = $view_mode_switch->getViewModeToSwitchTo($entity, $view_mode))) {
      $view_mode = $switch_to_view_mode;

      // Invalidate the cache for the entity.
      \Drupal::service('cache.render')->invalidateTags($entity->getCacheTags());
    }

Remaining tasks

User interface changes

API changes

Data model changes

πŸ› Bug report
Status

Closed: works as designed

Version

2.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States NicholasS

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @NicholasS
  • πŸ‡ΊπŸ‡ΈUnited States NicholasS
  • πŸ‡ΊπŸ‡ΈUnited States NicholasS

    This does appear to fix the behavior I found, however I don't know the exact ramifications on caching performance. Seems to always run clear the cache now, so its almost like the page is now uncached.

    So maybe a more experienced dev can find a better solution. But I guess this does validate an issue with page caching exists.

  • Pipeline finished with Failed
    9 months ago
    Total: 284s
    #95041
  • πŸ‡ΊπŸ‡ΈUnited States NicholasS

    Uploading a view of the behavior, along with showing how the current change appears to solve it.

  • πŸ‡©πŸ‡ͺGermany hctom

    Hi NicholasS, thank you for your report.

    Unfortunately I doubt that the View Mode Switch Field module is really the cause for this cache issue. It only uses core's hook_entity_view_mode_alter() hook and that one is called in EntityViewBuilder::getBuildDefaults() BEFORE any caching happens. Because this field is a "normal" Field API field, its current value is always respected in the cache entry being build later, while saving an entity also always invalidates any cached entries already (e.g. see EntityStorage::doPostSave() and respectively ::resetCache()).

    I just tried to reproduce your issue with a vanilla Drupal install and the Basic page content type having a view mode switch field for the Full view mode, but with no luck. When I enable the cache debug in services.yml, I can even see in the rendered markup, that there are cache keys used, that vary between the original view mode and the view mode to switch to (which will result in different cache entries):

    View mode not switched

    <!-- CACHE KEYS:
       * entity_view
       * node
       * 1
       * full
    -->
    

    View mode switched to teaser via View Mode Switch Field

    <!-- CACHE KEYS:
       * entity_view
       * node
       * 1
       * teaser
    -->
    

    In your video, the actually changing part of the node page is quite "far away" from the rest of your node (in the header above navigation and hero element), so I wanted to ask, if this is something you render programmatically or is the whole page created by your node template? I'm only asking, because if you render some parts programmatically, you have to make sure, that any of the node's cache tags/contexts/keys bubble up in the render pipeline and do not get lost, which in turn may result in caching issues.
    Another thing that makes me wonder, is that e.g. node A should never change cache entries of node B.

    Can you please have a look at the cache debug values in your project or if there may be something else, that might interfere with Drupal core's caching mechanisms on these pages. And just another question: Does the display also change when you reload the switched node several times after it has been cached?

  • πŸ‡ΊπŸ‡ΈUnited States NicholasS

    Thanks for the fast reply.

    so I wanted to ask, if this is something you render programmatically or is the whole page created by your node template?

    Yeah sorry some missing details. The default view of my pages is the page.html.twig and when I change the view mode the page should be using page--page--no-headers-or-footers.html.twig no-headers-or-footers being the name of the view mode. And yeah its just simple hardcoded HTML in that twig that sets a different header/footer.

    When I remove that cache clear I added, and I get that normal view mode when its not supposed to I see its using the default view mode.

    <!-- THEME DEBUG -->
    <!-- THEME HOOK: 'page' -->
    <!-- FILE NAME SUGGESTIONS:
       * page--page--.html.twig
       x page--node--page.html.twig
       * page--node--72661.html.twig
       * page--node--%.html.twig
       x page--node--page.html.twig
       * page--node.html.twig
       *
    

    Then I re-save the page and get the view mode suggestion.

    <!-- THEME DEBUG -->
    <!-- THEME HOOK: 'page' -->
    <!-- FILE NAME SUGGESTIONS:
       x page--page--no-headers-or-footers.html.twig
       * page--node--page.html.twig
       * page--node--72661.html.twig
       * page--node--%.html.twig
       * page--node--page.html.twig
       * page--node.html.twig
       * page.html.twig
    -->
    

    Let me dive into the cache debug in services.yml and report back with those findings.

  • πŸ‡ΊπŸ‡ΈUnited States NicholasS

    Ok I loaded up the page when it was the "default" view mode. Then saved the page which triggered the view mode switch, here is a diff of the cache markup
    https://www.diffchecker.com/fNijtehK/

    The right side when it says "

    <!-- CACHE-HIT: No -->

    " is when I see the view mode switch (no-headers-or-footers)

    I'll attach a video too as there is alot more cache tag info hope I got the right ones you wanted.

  • πŸ‡ΊπŸ‡ΈUnited States NicholasS

    Does the display also change when you reload the switched node several times after it has been cached?

    I only see the switched mode immediately after the node is saved. Refreshing a second time+ I see the default view mode continuously.

    I will add in another part of the site we are using this module, to display a field in another view mode and it works correctly. I know my needs might be non-typical use of the field, as I want it to "bubble up" a change so to speak to affect the header, aka use a different twig template.

    Maybe I am just miss-using the module? Love if it could help the my use case though! :D

  • πŸ‡ΊπŸ‡ΈUnited States NicholasS
  • πŸ‡©πŸ‡ͺGermany hctom

    Thanks for the details. That definitely helps identifying the potential problem. I again tried to reproduce this with a vanilla Drupal 10.2.3 installation and I can see, that the following theme suggestion are custom ones maybe defined by some code or another module:

       x page--page--no-headers-or-footers.html.twig
       * page--node--page.html.twig
       * page--node--72661.html.twig
    

    Core's theme suggestions β†’ are based on internal path only.

    So maybe the custom code providing those new suggestions does either not have the correct view mode available or itself some caching issues?

  • πŸ‡ΊπŸ‡ΈUnited States NicholasS

    Awe thanks, ok yeah must be my theme suggestions. I see I have something like

    function THEME_theme_suggestions_page_alter(array &$suggestions, array &$variables) {
    
      // Add suggestions for the page based off the node type and view mode
      $node = \Drupal::routeMatch()->getParameter('node');
      if ($node) {
        $suggestions[] = 'page__' . $node->getType() . '__' . $node->current_view_mode;
      }
    
    }
    

    and

    function THEME_preprocess_node(array &$variables, $hook)
    {
      if (isset($variables['view_mode'])) {
        $node = $variables['node'];
        $node->current_view_mode = $variables['view_mode'];
      }
    }
    

    I'll play with those, I guess when its cached those suggestions for some reason are not being fired.

  • Status changed to Closed: works as designed 9 months ago
  • πŸ‡ΊπŸ‡ΈUnited States NicholasS
  • πŸ‡ΊπŸ‡ΈUnited States NicholasS

    For anyone else who stumbles upon this and wants the solution.

    Seems when the page is cached, there is no $node->current_view_mode; so I had to extract the view mode out of the cache tags to maintain the same theme suggestions as non-cached pages.

    function THEME_theme_suggestions_page_alter(array &$suggestions, array &$variables) {
    // Add suggestions for the page based off the node type and view mode
      $node = \Drupal::routeMatch()->getParameter('node');
      if ($node) {
        // Get the nodes current view mode by extracting it from  cacheTags()array 0:"config:core.entity_view_mode.node.preview" its the last part of the string
        $cacheTags = $node->getCacheTags();
        // loop over cacheTags to find the view mode entity_view_mode and extract the view mode from the string
        foreach ($cacheTags as $cacheTag) {
          if (strpos($cacheTag, 'entity_view_mode') !== false) {
            $cache_node_view_mode = substr($cacheTag, strrpos($cacheTag, '.') + 1);
          }
        }
        $node_view_mode = $node->current_view_mode;
        // use either current_view_mode or the one from the cacheTags
        $node_current_view_mode = !empty($node_view_mode) ? $node_view_mode : $cache_node_view_mode;
        $suggestions[] = 'page__' . $node->getType() . '__' . $node_current_view_mode;
      }
    }
    
  • πŸ‡©πŸ‡ͺGermany hctom

    Thank you for your feedback and I'm glad I was able to help.

    ...but I am not really sure about your fix ;) I'd be careful with this, because of the quite hacky parsing of the view mode and in addition: I think you will have to add something to vary your page*.html.twig render cache entries per used suggestion. Maybe it is even enough to add a view mode based cache key in a hook_preprocess_page() implementation, but I am not really sure about this and would have to do some testing with this. Without this cache key Drupal may cache rendering of page.html.twig with the same cache entry as page--your-view-mode-based-suggestion.html.twig... but as I already said: That would require a lot more testing and debugging to see what really happens there ;)

Production build 0.71.5 2024