How do we set up a controller so the rendered output is cached?

Blocks are cached for logged in users by default, and the build function doesn't even run once the block is cached for logged in users.

I have some custom controllers for landing pages where I would like to cache the output for logged-in users.

I guess I could put everything within those controllers in a block or in several blocks, but is there a relatively simple way to make a controller behave like a block and not even run the build function once it is rendered for a logged in user?

2 Answers 2


One thing that you can do is the same as the entity view builder is doing, use a #pre_render callback.

If you look at \Drupal\Core\Entity\EntityViewBuilder::viewMultiple(), you can see it doesn't really do anything except prepare the render array and add a #pre_render callback to the whole thing.

The reason for that is that the pre_render callback will only be called on a cache miss. So you return your array with the cache keys/tags/contexts/max-age, the renderer service will attempt to load from the render cache. On a cache hit, it will return the previously cached HTML, on a miss, it will call the pre_render callback and you can do the things you want to do there.

  • +1 for the great info. One question, what's the difference, if viewMultiple() would do a complete build right away. Would that make the caching not prevent a rebuild on a cache hit?
    – 4uk4
    Nov 10, 2016 at 19:23
  • I don't really understand that question. If you want to elaborate in a separate question then I'm happy to answer there.
    – Berdir
    Nov 10, 2016 at 21:21
  • Never mind. My question was about this and I found the answer there: dev.acquia.com/blog/drupal-8-performance-render-caching. Thanks again for the insight, still have some trouble to completely grasp the entity view builder.
    – 4uk4
    Nov 10, 2016 at 21:59

You can make any render array or part of it cachable by setting cache keys. See this example from the entity view builder:

// Cache the rendered output if permitted by the view mode and global entity
// type configuration.
if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
  $build['#cache'] += array(
    'keys' => array(
    'bin' => $this->cacheBin,

The variable cacheBin is set to 'render'.

You can choose your own schema for the cache keys, dependent on the data you build the render array with. The cid to store the cache item is the result of the keys and has to be unique.

  • This caches the rendered output, but the controller will still rebuild the build array. I would like to return the rendered output if cache is set for it. I guess I should use $cache = \Drupal::cache()->get($cid)), but I'm not sure how to get the cid from the keys.
    – oknate
    Nov 10, 2016 at 16:44
  • 1
    The concept is, you cache those parts of the render array, which are expensive to build, like in this example an entity. Then it doesn't matter, if the controller is executed or not.
    – 4uk4
    Nov 10, 2016 at 16:52
  • Yeah, I'm starting to figure out that you have to leave rendered entities alone. You can do view and then build and pull individual fields out in a controller, but then you lose the benefit of the cacheable part from the view render array. I'm using custom controllers to display data from config_page entities, and I'm realizing that I should have been altering the build of the config pages view mode, and returning the render array of that rather than pulling items out of the entities build array and assembling in a custom controller and a custom theme function.
    – oknate
    Nov 10, 2016 at 16:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.