Allow 404s to be cacheable by dynamic page cache

Created on 30 May 2025, 6 days ago

Problem/Motivation

I noticed that 404s are not cacheable by Dynamic Page Cache. They get X-Drupal-Cache-Max-Age: 0 (Uncacheable) set.

Here's why:

  1. Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest catches ResourceNotFoundException when no route can be found for the request, and it wraps it in a NotFoundHttpException.
  2. Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber catches this exception and it makes a sub-request to render the 404 response page.
  3. The 404 response page's cache metadata is combined with the original exceptions cache metadata (of which there is none because it was a NotFoundHttpException and not a CacheableNotFoundHttpException.

Steps to reproduce

On a fresh Drupal install with the Dynamic Page Cache module enabled, visit any page that doesn't exist and observe the response headers in your browser. You'll see X-Drupal-Cache-Max-Age: 0 (Uncacheable).

Proposed resolution

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

πŸ› Bug report
Status

Active

Version

11.2 πŸ”₯

Component

cache system

Created by

πŸ‡ΊπŸ‡ΈUnited States bkosborne New Jersey, USA

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

Comments & Activities

  • Issue created by @bkosborne
  • πŸ‡ΊπŸ‡ΈUnited States bkosborne New Jersey, USA
  • πŸ‡ΊπŸ‡ΈUnited States bkosborne New Jersey, USA
  • πŸ‡¬πŸ‡§United Kingdom catch
    X-Drupal-Cache-Max-Age: 0 (Uncacheable)
    

    This doesn't apply to the dynamic page cache, only the internal page cache. There is a separate header for that:

    X-Drupal-Dynamic-Cache
    

    It does look like that's not cacheable with the standard profile though.

    πŸ“Œ Block status code visibility condition should use a status code cache context Active is related.

    Can you check whether the same problem exists with 11.1?

  • πŸ‡³πŸ‡ΏNew Zealand quietone

    If this problem was discovered on a version of Drupal that is not 11.x, add that information in the issue summary and leave the version at 11.x. In Drupal core changes are made on on 11.x (our main development branch) first, and are then back ported as needed according to the Core change policies β†’ . Also mentioned on the version β†’ section of the list of issue fields documentation.

  • πŸ‡¬πŸ‡§United Kingdom catch

    OK I double checked what's going on.

    The subrequest itself is cached in the dynamic page cache, e.g. this is what the cids look like in cache_dynamic_page_cache when you're logged out and browse to /abcdef

    | response:[request_format]=html:[route]=system.40435786c7117b4e38d0f169239752ce71158266ae2f6e4aa230fbbb87bd699c0e3                                                                                                                                               |
    | response:[cookies:big_pipe_nojs]=:[languages:language_interface]=en:[request_format]=html:[route]=system.40435786c7117b4e38d0f169239752ce71158266ae2f6e4aa230fbbb87bd699c0e3:[session.exists]=0:[theme]=olivero:[url7cOhVamWazkPEBofpmp46A6Nx9YVZ6IP27D-joYI6AY |
    

    What doesn't get cached in dynamic_page_cache is request for the 404 page itself, e.g. /abcdef.

    However, caching this would not really work in the context of dynamic_page_cache - it caches by route rather than per path, and by definition /abcdef can't be routed, if it could it wouldn't be a 404.

    If we cached by path in this case, then there is no particular reason it couldn't be cached, but the internal page cache module (page_cache) already successfully caches the 404 response by path. This means the dynamic_page_cache would have a very low cache hit rate. Similarly, because the response sends cacheable http headers, varnish or a CDN can cache the HTML at the edge too. Once it's in either the internal page cache or an edge cache, then it won't hit dynamic_page_cache anyway.

    Did some quick profiling of what it looks like with xhprof, and found πŸ“Œ Avoid preloading routes in subrequests Active which would be quick win in this situation though. There might be other things we can optimize away too.

Production build 0.71.5 2024