How to use Page Cache with multiple themes, Drupal 9

Page Cache is a great drupal core module that allows to serve cached versions of the pages to anonymous users.

How this works, basically when a request arrives to drupal, drupal checks if there is a version of that page in the cache (checking by the url).

If the cache doesn’t have an instance of the page, the whole drupal execution is done and the result is saved in cache and served to the browser.

If there is a cached version, this version is served.

As you can see, since the drupal execution is reduced and we are serving previously calculated data, when we have something cached it is really fast.

This works great for anonymous users, since that is the vast majority of the users on the internet.

This feature is great for content based sites, like digital magazines, digital newspapers, documental sites, wikis, blogs, enterprise sites. Where we

have a heavy editorial content production with a big traffic of anonymous users.

This module most of the times works as default and with no other customizations, however, I had a situation, that is why this post.

We have a site that is running a regular theme for desktop and mobile, however for node pages we have a custom AMP theme using the AMP Module.

By default, the AMP module allows to render an AMP page, using the AMP theme, if the argument amp is added to the query string. For example

  • http://www.mysite.com/node/42 – Normal theme
  • http://www.mysite.com/node/42?amp – AMP theme

The bad side of this mechanism is that it does not follow the best SEO practices. As per our SEO Expert recommendations, every page must have a unique url, otherwise the page will be flagged as duplicate content and that is not good.

You can say, as I did to my SEO expert, but drupal is using the canonical metatag to point to the right page!. However, as per my SEO expert, it is still a bad practice to have multiple urls for a single page.

The solution is to expose a unique url and serve the AMP or the NORMAL theme depending on the device that the visitor is using. This is what is commonly called Full AMP. I’ll have to explain how to do this in another post to not extend this post too much, however, I think the problem is clear.

We have a url that can serve different versions depending on the device of the user, keep in mind that AMP is for mobile devices primarily.

The page cache module is affected by this, because it will cache a version of the page for the first device that accesses the url and will serve the same version to all further visitors. This will cause an AMP page to be presented on a Desktop Browser, or in a mobile device the AMP can not be presented.

In order to solve this problem, I have tried to cache and serve both versions to the users depending on the device (I’m doing this because is my business requirement, in other scenarios the requirement may change).

What the Page Cache Module does, is to generate a key and save the cache version using that key. By default Page Cache generates the key using the url, the scheme and the request format.

You can see in the PageCache class of the service, in the method getCacheId, how the key is generated.

For my problem, I want to add an extra element to the key, the device type, if it is a mobile or not. I need to alter this method in some way.

Drupal has the alternative to override services (same thing that I did to alter AMP, will explain in another post), you can see here how to do it.

Usually I override services using the my_module.services.yml and redefine the service key that I want to override in my module. But for this particular service it didn’t work, so I have used the first alternative.

I have created a custom class that extends ServiceProviderBase and then I have defined the alter method.

Finally, my code looks something like this. My module is called my_custom.

The MyCustomServiceProvider class is located in my_custom/src/MyCustomServiceProvider.php and it looks like this

<?php

namespace Drupal\my_custom;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;

class MyCustomServiceProvider extends ServiceProviderBase {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    if ($container->hasDefinition('http_middleware.page_cache')) {
      $definition = $container->getDefinition('http_middleware.page_cache');
      $definition->setClass('Drupal\my_custom\StackMiddleware\PageCache');
    }
  }
}

Then, I have created the class to override the default PageCache, that extends from PageCache. This file is located into

my_custom/src/StackMiddleware/PageCache and it has this code

<?php

namespace Drupal\my_custom\StackMiddleware;

use Drupal\page_cache\StackMiddleware\PageCache as DefaultPageCache;
use Symfony\Component\HttpFoundation\Request;

class PageCache extends DefaultPageCache {

  /**
   * @inheritDoc
   *
   */
  protected function getCacheId(Request $request) {
    $md = \Drupal::service('mobile_detect');
    $md->setDetectionType(\Mobile_Detect::DETECTION_TYPE_EXTENDED);

    $amp = ($md->isMobile() || $md->is('Bot'));

    if (!isset($this->cid)) {
      $cid_parts = [
        $request->getSchemeAndHttpHost() . $request->getRequestUri(),
        $request->getRequestFormat(NULL),
        $amp,
      ];
      $this->cid = implode(':', $cid_parts);
    }
    return $this->cid;
  }
}

As you can see in this class, I’m using Mobile Detect to identify the device, and since Google wants to see the AMP version we try to serve the AMP version to Google and other bots as well.

I hope this post can help others in similar situations since there is not to much documentation around about how to do this.

Keep tuned for the next post about how to customize the AMP module to get a Full AMP experience in your site and Improve your SEO.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.