Performance, Caching, and Security
What to know
Drupal's render pipeline is cacheable: render arrays carry #cache with keys, contexts, and tags. Cache contexts vary output (e.g., user.roles, url.path); cache tags invalidate (e.g., node:5, config:system.site); max-age sets lifetime. The Internal Page Cache serves anonymous pages; Dynamic Page Cache caches rendered responses for authenticated users; BigPipe streams placeholders.
Cache invalidations happen when entities save (tags), config changes, or cron clears. drush cr rebuilds caches. Varnish/CDN sit in front; Acquia uses Varnish with tag-based purging.
Performance levers: enable CSS/JS aggregation, use lazy image loading, avoid #cache max-age: 0 unless necessary, prefer cache.backend.* (Redis/Memcache) for high traffic, and keep views paginated.
Security: output escaping (Twig auto-escape), input validation, CSRF tokens on forms, and the permission system. Avoid |raw on user data. Use Xss::filter() for allowed tags. SQL injection is prevented by placeholders. File uploads need extension validation. The security advisory policy means core and contrib get SA releases; keep composer outdated in check.
Traps
- Contexts vs. tags: contexts vary cache keys; tags invalidate. Mixing them up is a classic question.
max-age: 0disables caching for that element-sometimes required, often a mistake.- BigPipe vs. Dynamic Page Cache: both serve authenticated users; BigPipe streams, DPC caches.
|rawbypasses escaping and is a common XSS trap.- Varnish purge relies on cache tags; missing tags mean stale content.
Rule of thumb
If output depends on who or where, add a cache context. If it depends on what data, add a cache tag. If it must never cache, set max-age: 0 deliberately.
Worked example
A block showing the current user's name needs contexts: ['user']; a block listing nodes needs tags: ['node_list'] so new nodes invalidate it.