Back

Loading…

Documentation

👤 Anonymous vs authenticated: cómo cambia la estrategia de caché en Drupal

Hasta ahora hemos hablado de caché como si todas las visitas fueran iguales.

Pero no lo son.

En Drupal existe una diferencia importante entre:

anonymous users

y:

authenticated users

Ambos pueden beneficiarse de caché.

Pero no de la misma manera.

Y entender esta diferencia es fundamental para evitar dos errores muy comunes:

desactivar demasiada caché porque hay usuarios autenticados

o:

intentar cachear como pública una respuesta que realmente depende de sesión o usuario


🧠 Empecemos por la idea más simple

Un usuario anónimo suele tener menos estado asociado.

Conceptualmente:

Anonymous visitor
→ no account-specific state
→ fewer variations
→ easier to reuse responses

Un usuario autenticado puede introducir:

permissions
roles
toolbar
user-specific content
messages
preferences
session data

Eso hace que el número de variantes potenciales aumente.

Pero no significa:

authenticated
=
uncacheable

Ese sería un error conceptual.

Drupal está diseñado precisamente para cachear también páginas dinámicas y personalizadas.


📄 Internal Page Cache está pensado principalmente para anónimos

Drupal Core incluye:

Internal Page Cache

o:

page_cache

Su objetivo principal es cachear respuestas completas para usuarios anónimos.

La razón es bastante intuitiva.

Si dos visitantes anónimos reciben exactamente:

same URL
same content
same representation

podemos reutilizar una respuesta completa.

Conceptualmente:

Anonymous A
GET /about
      ↓
generate page
      ↓
cache

Anonymous B
GET /about
      ↓
reuse full response

Esto puede ser extremadamente eficiente.

Drupal documenta Internal Page Cache precisamente como una solución orientada a tráfico anónimo. (drupal.org)


⚠️ Pero “anonymous” no siempre significa “stateless”

Aquí está la primera complicación.

Un usuario anónimo puede tener:

session
cart
cookie preferences
language preference
temporary state

Por ejemplo:

Anonymous A
Cart: 3 products

Anonymous B
Cart: empty

Ambos son anónimos desde el punto de vista de Drupal.

Pero sus respuestas no son iguales.

Eso significa que no podemos asumir:

anonymous
=
same response for everyone

La documentación de Drupal advierte expresamente sobre sitios que muestran contenido personalizado por sesión a usuarios anónimos, como shopping carts. En estos casos Internal Page Cache requiere especial atención. (drupal.org)


🧩 Aquí entra Dynamic Page Cache

Drupal también incluye:

Dynamic Page Cache

o:

dynamic_page_cache

Esta capa es especialmente importante porque no necesita pensar la página como un bloque indivisible.

Puede reutilizar gran parte del render tree aunque una pequeña sección sea dinámica.

Imaginemos:

Page
├── Header
├── Menu
├── Main content
├── Sidebar
└── User block

Quizá:

Header
Menu
Main content
Sidebar

pueden reutilizarse.

Y solo:

User block

depende del usuario actual.

Dynamic Page Cache permite aprovechar esa diferencia.


🧠 Authenticated no significa recalcular todo

Supongamos un usuario autenticado que visita:

/news/article-42

La página podría contener:

Article title
Article body
Image
Related articles
Main navigation
Footer
User toolbar
Personal menu

Sería muy costoso asumir:

because toolbar is personal
→ render entire page again

En realidad Drupal puede mantener cacheable una gran parte.

Conceptualmente:

Article
→ shared

Navigation
→ maybe shared by permissions

Footer
→ shared

Toolbar
→ user/permissions dependent

Eso es mucho más eficiente.


🌍 Cache contexts son fundamentales para usuarios autenticados

En el artículo anterior vimos que los cache contexts representan:

en qué situación cambia el output

Con usuarios autenticados aparecen contextos especialmente importantes.

Por ejemplo:

user
user.roles
user.permissions

No significan lo mismo.


👤 user

El contexto:

user

significa que el resultado puede variar por usuario individual.

Conceptualmente:

User 42
→ cached variant A

User 99
→ cached variant B

User 120
→ cached variant C

Esto puede ser correcto si mostramos algo realmente personal.

Por ejemplo:

Hello, Daniel

o:

Your saved items

Pero tiene alta cardinalidad.

En un sitio con miles de usuarios puede generar muchísimas variantes.


🎭 user.roles

Ahora supongamos que un bloque cambia solamente según el rol.

Por ejemplo:

editor
administrator
customer

Podría ser suficiente usar:

user.roles

Conceptualmente:

Editors
→ one variant

Administrators
→ another variant

Customers
→ another variant

Eso puede ser mucho más eficiente que una variante por usuario.


🔐 user.permissions

A veces el resultado depende realmente de permisos.

Por ejemplo:

Can edit content?
Can access administration pages?
Can view unpublished content?

Entonces:

user.permissions

puede expresar mejor la dependencia.

Drupal utiliza cache contexts jerárquicos y optimizables para evitar variaciones innecesarias. (drupal.org)

La idea práctica es:

usar el contexto menos específico que siga siendo correcto


📈 Cardinalidad importa

Podemos visualizarlo así:

language
→ 2–5 variants

role
→ maybe 5–20 variants

permissions
→ potentially more

user
→ thousands or millions

Cuantas más variantes generamos, menos reutilización conseguimos.

Por eso una buena cacheability metadata no solo debe ser correcta.

También debe ser razonablemente específica.


🔐 Las sesiones cambian el panorama

Una sesión puede introducir estado.

Por ejemplo:

logged-in user
shopping cart
temporary form state
messages
wizard progress

Si una respuesta depende de la sesión, tenemos que tratarla con mucho más cuidado.

Pero nuevamente:

la existencia de sesión no significa que toda la página tenga que ser dinámica

La clave es aislar la parte que depende de esa sesión.


💬 Drupal messages

Un ejemplo clásico son los mensajes:

Your content has been saved.

Ese mensaje pertenece a un request o usuario concreto.

No queremos que:

User A
sees "Your content has been saved"

y después:

User B
receives same cached message

Drupal debe mantener ese contenido separado.

Este tipo de elementos ayuda a entender por qué una página autenticada tiene necesidades distintas a una página pública simple.


🛠️ Toolbar

La Toolbar de Drupal es otro buen ejemplo.

Un administrador puede ver:

Manage
Shortcuts
Administration

mientras otro usuario autenticado puede no ver nada de eso.

La página principal puede ser la misma.

Pero ese pequeño componente cambia según permisos.

La estrategia ideal es:

shared page content
+
permission-dependent component

no:

entire page uncacheable

🧩 Render cache sigue siendo útil

Incluso cuando la página completa no puede reutilizarse como una sola respuesta, Render Cache puede seguir ayudando muchísimo.

Por ejemplo:

Node 42

puede tener un render cache reutilizable.

Una View puede tener su propio resultado cacheable.

Un menú también.

Un bloque también.

Conceptualmente:

Authenticated request
      ↓
Page assembled
      ↓
many components already cached
      ↓
only some pieces recomputed

Por eso debemos evitar pensar únicamente en:

full page HIT

como medida de éxito.


🧠 Una página autenticada puede ser rápida sin Page Cache

Supongamos:

X-Drupal-Cache
→ not applicable

pero:

Dynamic Page Cache
→ useful
Render Cache
→ useful
Entity render cache
→ useful
Views cache
→ useful

La experiencia puede seguir siendo excelente.

La pregunta correcta no es:

“¿Por qué no veo full-page cache?”

sino:

“¿Cuánto trabajo está reutilizando Drupal dentro de este request?”


💤 Lazy builders son especialmente útiles aquí

Drupal permite usar:

#lazy_builder

para componentes dinámicos.

Imaginemos:

Page
├── Article
├── Related content
├── Footer
└── User notifications

Podemos mantener:

Article
Related content
Footer

cacheados.

Y resolver:

User notifications

de forma lazy.

Conceptualmente:

cached shell
+
dynamic fragment

Eso ayuda muchísimo cuando existe personalización limitada.


🚀 BigPipe

Drupal Core incluye BigPipe.

La idea es enviar primero lo que ya podemos renderizar rápidamente:

Page shell

y completar después placeholders dinámicos.

Conceptualmente:

Request
   ↓
Send cached/static parts
   ↓
Browser starts rendering
   ↓
Resolve dynamic placeholders
   ↓
Inject them

Esto es especialmente útil en páginas autenticadas.

Porque aunque ciertos componentes dependan de usuario o sesión, el visitante no tiene por qué esperar a que toda la página termine de calcularse antes de ver algo.


🧠 BigPipe no sustituye una buena cacheability

Esto merece subrayarse.

BigPipe funciona bien cuando Drupal entiende:

what is cacheable
what is dynamic

Si marcamos toda la página:

max-age: 0

sin necesidad, estamos dificultando ese trabajo.

BigPipe no es un reemplazo de:

tags
contexts
max-age

Es una herramienta que se beneficia de que esos conceptos estén bien definidos.


🛒 Anonymous + cart: un caso especialmente interesante

Drupal Commerce nos obliga a pensar más cuidadosamente.

Supongamos un visitante anónimo:

User A
cart = 2 items

y otro:

User B
cart = empty

Una homepage podría contener:

Header
Hero
Products
Mini-cart
Footer

Quizá solamente:

Mini-cart

depende del carrito.

Sería muy costoso convertir:

Hero
Products
Footer

en contenido completamente dinámico solamente por ese bloque.

La estrategia ideal suele parecerse a:

Shared/cached page
+
dynamic cart fragment

Este caso será central cuando entremos específicamente en Drupal Commerce.


🍪 Cookies y personalización

También debemos distinguir entre diferentes tipos de cookies.

Por ejemplo:

analytics cookie
consent cookie
session cookie
cart-related cookie

No todas tienen las mismas consecuencias.

Y una cookie existente no significa automáticamente:

everything private

Pero sí debemos entender:

¿la respuesta realmente cambia según esa cookie?

Si cambia, necesitamos representar esa dependencia correctamente o aislarla.


🌐 HTTP caching y usuarios autenticados

Aquí aparece otro nivel.

Un browser puede cachear contenido privado para su propio usuario.

Pero una caché compartida, como un CDN, tiene requisitos mucho más estrictos.

No queremos que una respuesta personalizada para:

User A

termine siendo servida a:

User B

Por eso headers como:

Cache-Control: private

son completamente legítimos en determinadas respuestas personalizadas.

La optimización no consiste en eliminar private indiscriminadamente.

Consiste en decidir:

What is truly private?
What can be shared?

🔐 Public vs private

Podemos pensar:

public
→ shared caches may reuse it

private
→ intended for a single user's cache

Una página completamente pública puede ser candidata para:

CDN
Varnish
shared reverse proxy

Una página personalizada puede requerir:

private

Pero quizá algunos de sus recursos o fragments siguen siendo compartibles internamente por Drupal.

Ahí está la diferencia entre:

HTTP response caching

y:

Drupal internal caching

🧪 Cómo comparar anonymous y authenticated

Una técnica sencilla es analizar la misma URL de dos maneras.

Por ejemplo:

/home

Primero:

curl -sI https://example.com/

como visitante anónimo.

Después revisar la misma ruta dentro de una sesión autenticada usando DevTools.

Comparar:

Cache-Control
X-Drupal-Dynamic-Cache
Set-Cookie
Vary

y, en desarrollo:

X-Drupal-Cache-Contexts

Esto puede revelar inmediatamente qué cambia.


🔎 Preguntas útiles

Cuando una página autenticada parece lenta:

¿Qué parte depende realmente del usuario?

Whole page?
Toolbar?
Notification?
Saved state?

¿Estamos usando user cuando bastaría user.permissions?

¿Hay un componente con max-age: 0?

¿Puede aislarse mediante lazy builder?

¿Dynamic Page Cache está activo?

¿Estamos confundiendo “no full-page cache” con “no caching”?

Estas preguntas suelen ser mucho más productivas que intentar forzar un HIT.


🚫 No desactivar caché globalmente para usuarios autenticados

Una solución tentadora puede ser:

authenticated users
→ no cache

Eso desperdicia una enorme cantidad de trabajo.

Incluso una página altamente personalizada puede contener componentes como:

site logo
navigation structure
content entities
media
configuration
translations

que pueden reutilizarse.

Drupal está diseñado para manejar precisamente este tipo de composición.


🔴 Otro anti-pattern: max-age: 0 en bloques personalizados

Supongamos que hacemos:

public function build() {
  return [
    '#markup' => $this->currentUser()->getDisplayName(),
    '#cache' => [
      'max-age' => 0,
    ],
  ];
}

Funciona.

Pero quizá estamos diciendo demasiado.

Podríamos expresar:

varies by user

con un cache context:

'#cache' => [
  'contexts' => [
    'user',
  ],
]

y mantener el resultado cacheable por usuario.

La diferencia es enorme.


🧠 Dynamic no significa uncacheable

Esta es probablemente la frase clave del artículo:

Contenido dinámico no es necesariamente contenido no cacheable.

Algo puede variar.

Y seguir siendo cacheable.

Por ejemplo:

Language-dependent
→ variants by language

Permission-dependent
→ variants by permissions

User-dependent
→ variants by user

La caché no requiere que exista una única respuesta universal.

Requiere que Drupal sepa cuándo dos requests son equivalentes.


🧩 Cache contexts responden exactamente esa pregunta

Podemos pensar:

Request A
and
Request B

pueden reutilizar el mismo resultado si sus contextos relevantes coinciden.

Por ejemplo:

same language
same permissions
same route

Drupal puede reutilizar una variante.

Si cambia alguno:

different language

selecciona otra.

Ese es el verdadero poder de los cache contexts.


⚖️ Seguridad y caché están relacionadas

Una metadata de caché incorrecta no es solamente un problema de rendimiento.

También puede convertirse en un problema funcional o incluso de seguridad.

Supongamos que algo personalizado no declara:

user

como contexto.

Drupal podría reutilizar una respuesta entre usuarios cuando no debería.

Por eso:

Cacheability correcta significa tanto rendimiento como aislamiento correcto del contenido.

Nunca debemos eliminar contexts solo para aumentar los HITs.


🔐 Más caché no siempre es mejor

Si la información debe ser privada:

keep it private

Si depende de usuario:

vary by user

Si no puede reutilizarse:

don't cache it

La optimización correcta es:

máxima reutilización compatible con la corrección y la seguridad

No:

“cachear todo.”


📊 Medir ambos escenarios

Cuando hacemos pruebas de rendimiento conviene medir:

Anonymous
Authenticated

por separado.

Por ejemplo:

Homepage anonymous
TTFB: 70 ms

Homepage authenticated
TTFB: 180 ms

Eso no significa necesariamente que exista un problema.

El usuario autenticado puede requerir más trabajo.

La pregunta es:

Can we explain that difference?

Y:

Can we reduce unnecessary dynamic work?

🧭 Nuestro modelo mental

Podemos resumirlo así:

Anonymous
   ↓
Can often use full page cache
   ↓
Dynamic Page Cache still useful

mientras:

Authenticated
   ↓
Full response usually more contextual
   ↓
Dynamic Page Cache
Render Cache
Lazy Builders
BigPipe
become especially important

Y para ambos:

correct cacheability metadata
=
foundation

✅ Checklist para usuarios autenticados

Cuando revisemos una página autenticada:

[ ] ¿Dynamic Page Cache está activo?
[ ] ¿Qué cache contexts tiene?
[ ] ¿Aparece `user`?
[ ] ¿Realmente necesita `user`?
[ ] ¿Bastaría `user.roles` o `user.permissions`?
[ ] ¿Existe `max-age: 0`?
[ ] ¿Qué componente lo introduce?
[ ] ¿Hay sesión?
[ ] ¿Qué depende de esa sesión?
[ ] ¿Puede aislarse con lazy builder?
[ ] ¿BigPipe está ayudando?
[ ] ¿Estamos midiendo TTFB?

✅ Checklist para usuarios anónimos

Y para anonymous:

[ ] ¿Internal Page Cache está activo?
[ ] ¿Dynamic Page Cache está activo?
[ ] ¿La página es realmente igual para todos?
[ ] ¿Existe sesión anónima?
[ ] ¿Hay carrito?
[ ] ¿Hay personalización por cookie?
[ ] ¿Qué devuelve Cache-Control?
[ ] ¿MISS se convierte en HIT?
[ ] ¿Estamos creando cookies innecesarias?

🧠 La idea principal

Si debemos quedarnos con una sola idea:

Drupal no divide el mundo entre “páginas estáticas cacheables” y “usuarios autenticados no cacheables”.

Tiene un modelo mucho más granular.

Puede decir:

this component
depends on language

this one
depends on permissions

this one
depends on user

this one
depends on cart

this one
is shared by everyone

y ensamblar la respuesta correctamente.

Por eso Dynamic Page Cache, Render Cache, cache contexts, lazy builders y BigPipe son tan importantes.

La pregunta correcta no es:

“¿Esta página es dinámica?”

sino:

“¿Qué parte es dinámica, para quién cambia y qué podemos seguir reutilizando?”


🔜 En el siguiente artículo

Hay un caso donde todas estas ideas se vuelven especialmente interesantes:

Drupal Commerce

Un carrito depende de sesión.

Los precios pueden variar.

Las promociones pueden cambiar.

El mini-cart aparece en páginas que, por lo demás, son completamente públicas.

Entonces aparecen preguntas como:

  • ¿El carrito debería ser cacheable?
  • ¿Por qué un mini-cart puede afectar a toda la página?
  • ¿Cómo evitar que Commerce convierta páginas públicas en respuestas innecesariamente dinámicas?
  • ¿Qué papel tienen lazy builders y placeholders?

👉 En el siguiente artículo veremos 🛒 “Drupal Commerce y caché: cómo mantener rápido un sitio con carrito, precios y sesión”.

Contents