TL;DR — Quick Summary
Drupal sites are slow because of underutilized cache layers (most sites use 30–50% of available caching), contrib module bloat (each adds DB queries and JS), unoptimized Views (50–200+ queries per uncached page), and front-end delivery issues. Quick wins: 1) Verify Internal Page Cache module is enabled (serves anonymous users in < 10ms). 2) Verify Dynamic Page Cache module is enabled (caches authenticated pages). 3) Enable Views caching (tag-based for Drupal 9+). 4) Add database indexes on Views filter/sort fields. 5) Profile contrib modules — disable one at a time, measure TTFB. 6) Use 'Fields' display in Views instead of 'Content' to reduce entity loading. 7) Inline critical CSS and defer the full stylesheet. 8) Add Redis as the cache backend.
Key Takeaways
- ✓Drupal has 4+ cache layers — Internal Page Cache, Dynamic Page Cache, render cache (entity/block level), and external layers (Varnish, Redis, CDN). Most sites properly use only 1–2.
- ✓Internal Page Cache serves anonymous users in < 10ms — verify it's enabled (it should be by default on Drupal 8+).
- ✓Dynamic Page Cache caches authenticated pages with placeholders for personalized content — a unique Drupal feature.
- ✓Cache tags enable surgical cache invalidation without full purges — Drupal's most powerful caching feature.
- ✓Views queries generate 50–200+ database queries per page when uncached — enabling Views caching is critical.
- ✓Contrib module bloat follows the same pattern as WordPress plugins — each module adds queries, JS, and CSS.
- ✓Drupal's render pipeline (entities → render arrays → HTML) provides multiple caching opportunities, but each layer must be configured.
Why Drupal Sites Load Slowly
Despite having the most powerful caching architecture of any CMS, most Drupal sites are slow. The reasons:
1. Underutilized cache layers: Drupal provides Internal Page Cache (anonymous full-page), Dynamic Page Cache (authenticated page caching with personalization placeholders), render cache (entity and block level with cache tags for precise invalidation), and hooks for external layers (Varnish, Redis, CDN). Most sites have the core cache modules enabled but don't optimize cache lifetimes, don't add Redis/Memcached as backends, and don't implement Varnish for HTTP-level caching. The result: anonymous requests that could be served in < 10ms take 1–5 seconds.
2. Contrib module overhead: Each contrib module potentially adds database queries, JavaScript files, CSS files, and processing hooks to every page load. Sites with 50+ active modules commonly have 100–200 database queries per page. Some modules add processing to every request even when their functionality isn't displayed on the current page.
3. Views query explosion: Views (Drupal's content listing system) is extremely powerful but generates many database queries when misconfigured. A Views page displaying 20 content items with relationships, custom fields, and taxonomies can generate 150+ database queries without caching. Missing database indexes on filtered/sorted fields cause full table scans.
4. Entity loading overhead: Drupal's entity system (nodes, taxonomy terms, users, paragraphs) provides rich content modeling but has a performance cost. Loading a 'full' entity loads all its fields, references, and computed values — even if the display only shows a title and image. The N+1 query problem (one query to get the list, then N queries to load each entity's fields) is common.
5. Front-end delivery: Many Drupal themes still load render-blocking CSS/JS globally, include jQuery and jQuery UI on every page (even when unused), serve unoptimized images, and use heavy contributed themes with excessive CSS.
Mastering Drupal's Cache Architecture
Drupal's cache system is its biggest performance advantage — when fully configured.
Internal Page Cache (core module):
- •Caches complete HTML output for anonymous visitors
- •Should be enabled by default (verify: Admin → Extend → search 'Internal Page Cache')
- •Serves cached pages without executing any PHP beyond the bootstrap
- •TTFB drops to < 10ms for cached pages
- •Invalidated via cache tags when content changes
Dynamic Page Cache (core module):
- •Caches pages for authenticated users with personalization
- •Uses `#cache` render array properties and `auto_placeholder_conditions`
- •Personalizable parts (user name, cart count) are rendered as placeholders and filled per-request
- •Dramatically reduces processing for authenticated users
- •Verify enabled: Admin → Extend → search 'Dynamic Page Cache'
Render Cache (entity/block level):
- •Caches individual rendered entities and blocks
- •Uses cache tags for precise invalidation: when a node is updated, only that node's cache (and pages containing it) are invalidated
- •Cache contexts determine cache variations (user role, language, URL)
- •Cache max-age controls expiration
- •Proper `#cache` metadata on render arrays is critical for this layer
Cache tags (Drupal's killer feature): Cache tags are Drupal's mechanism for surgical cache invalidation:
- •When node 42 is updated, all cache entries tagged with `node:42` are invalidated
- •When a taxonomy term changes, all entries tagged with `taxonomy_term:15` are invalidated
- •No need for full cache purges — only affected content is regenerated
- •This enables aggressive cache lifetimes (hours/days) with instant invalidation on content changes
External cache layers:
- •Redis: In-memory cache backend. Replace Drupal's default database cache backend with Redis for 10–100x faster cache reads. Install the Redis module and configure in settings.php.
- •Memcached: Alternative to Redis for object caching. Simpler, slightly less feature-rich.
- •Varnish: HTTP reverse proxy cache. Serves cached responses before Drupal even bootstraps. Supports Drupal's cache tags via the Purge module for instant invalidation. Can serve pages in < 1ms.
- •CDN: Cloudflare, Fastly, or AWS CloudFront for static asset delivery and optional full-page caching at the edge.
Optimal cache stack for production Drupal:
- 1Internal Page Cache → enabled
- 2Dynamic Page Cache → enabled
- 3Redis → as default cache backend (replaces database cache tables)
- 4Varnish → HTTP cache in front of Drupal (with Purge module for tag-based invalidation)
- 5CDN → for static assets and optional edge caching
Module Profiling and Cleanup
Contrib module management is critical for Drupal performance.
Profiling with Webprofiler (recommended method):
- 1Install and enable Devel module with Webprofiler sub-module
- 2Navigate to key pages and examine the toolbar: database queries, cache hits, memory, render time
- 3Disable ONE module at a time
- 4Refresh and compare metrics
- 5Create a spreadsheet: module name, queries added, memory impact, render time impact, business value
Profiling with Blackfire.io (enterprise method): Blackfire provides function-level profiling showing exactly which modules consume the most time:
- •Install Blackfire agent and PHP probe
- •Profile key page requests
- •Analyze call graphs to identify slow modules and functions
- •Compare profiles before and after module changes
Common high-impact module categories:
- •Search modules (Search API, Solr): Can add complex indexing queries. Ensure search index is on a separate server for large sites.
- •Media modules: Image styles generate on first request. Pre-generate image derivatives for critical images.
- •Paragraph types: Deeply nested paragraphs create entity loading chains. Limit paragraph nesting depth.
- •Social modules: Often load external JavaScript. Replace with static share links.
- •Statistics/analytics: Use GA4 via GTM instead of Drupal-based analytics modules.
- •Pathauto/Redirect: URL alias and redirect lookups add database queries per request. Ensure proper indexing on alias tables.
Cleanup rules:
- •Uninstall (not just disable) unused modules — disabled modules can still load code
- •Consolidate overlapping modules (don't use 3 modules for SEO when one covers everything)
- •Review hook implementations — modules that hook into `hook_page_attachments` add assets to every page
- •Use Drupal's core functionality (added in 8/9/10) instead of contrib where possible
Views Query Optimization
Views is Drupal's most powerful and most frequently performance-abused feature.
Enable Views caching (highest priority):
- •Edit each View → Advanced → Caching
- •For Drupal 9+: Use Tag-based caching (cache entries invalidate when underlying content changes — safe and performant)
- •For older versions: Use Time-based caching (set appropriate duration — 1 hour for most listings)
- •Without caching, Views re-executes all queries on every page load
Reduce query count with display mode:
- •'Content' display (default): Loads full entities with all fields → N+1 query problem → 100+ queries for 20 items
- •'Fields' display: Selects only specified fields from the database → typically 1–3 queries total
- •Switch to 'Fields' display for Views that don't need full entity rendering (listing pages, teasers, search results)
Database indexing for Views:
- •Identify fields used in Views filters, sorts, and contextual filters
- •Add database indexes on these fields: `CREATE INDEX idx_field_name ON node__field_name (field_name_value)`
- •Missing indexes cause full table scans — adding them can reduce query time from 500ms to < 5ms
- •Use `EXPLAIN` on slow Views queries to identify missing indexes
Pagination: Always paginate Views with more than 20 results. Loading 100+ entities creates excessive database load and DOM size.
Relationship optimization:
- •Minimize entity reference relationships in Views (each adds JOIN queries)
- •Use rendered entity reference fields instead of Views relationships where possible
- •Cache entities that are frequently referenced (taxonomy terms, referenced nodes)
Common Views performance anti-patterns:
- •Views blocks on every page without caching (header, sidebar, footer blocks)
- •Multiple uncached Views on a single page
- •Views with complex filters on unindexed fields
- •'Content' display mode for simple listing pages
- •Global text fields with token replacements (each token requires entity loading)
Struggling with Drupal speed?
Our team optimizes Drupal sites for real-world results. Request an audit.
Front-End Delivery Optimization
Drupal's front-end delivery has improved significantly in recent versions, but many sites still have legacy issues.
Asset management (Drupal 9+):
- •Use Drupal's library system to declare CSS/JS dependencies properly
- •Libraries load only on pages where they're attached (not globally)
- •Override `*.libraries.yml` to remove unused library dependencies
- •Aggregate CSS and JS in production: Admin → Configuration → Performance → check both aggregation options
Critical CSS:
- 1Generate critical CSS per page type (article, taxonomy listing, homepage)
- 2Inject inline via `hook_page_attachments_alter()` or a custom module
- 3Defer the full stylesheet:
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
JavaScript optimization:
- •Drupal 10 removed jQuery as a hard dependency — verify your theme doesn't load it unnecessarily
- •Add the `defer` attribute to all non-critical JavaScript via library definitions
- •Use Drupal's `drupalSettings` for passing data to JS instead of inline scripts
- •Implement dynamic imports for heavy interactive components
Image optimization:
- •Use Drupal's responsive image module for `
<picture>` elements with appropriate breakpoints - •Configure image styles for each display context (thumbnail, teaser, full)
- •Add `loading="lazy"` to below-fold images (Drupal 10+ includes this by default)
- •Enable WebP generation via image styles or the WebP module
- •Preload the LCP image: use `hook_page_attachments()` to add `
<link rel="preload">` for hero images
Font optimization:
- •Self-host web fonts in your theme
- •Use `font-display: swap` in @font-face declarations
- •Preload the primary font file
- •Subset fonts for needed character ranges only
- •Use size-adjusted fallback fonts for CLS prevention
Database Optimization
Drupal's database layer needs regular maintenance, especially for content-heavy sites.
Table maintenance:
- •Run `OPTIMIZE TABLE` on large tables quarterly: `node`, `node_field_data`, `cache_*`, `sessions`, `watchdog`
- •Truncate the watchdog (log) table regularly or set maximum row limits
- •Clean expired sessions: Drupal does this automatically, but verify the cron runs
- •Purge old cache tables: `drush cr` clears all caches; target specific bins with `drush cc`
Index optimization:
- •Add indexes on entity reference fields used in Views relationships
- •Index custom fields used in Views filters/sorts
- •Use `EXPLAIN ANALYZE` on slow queries to identify full table scans
- •Monitor with MySQL slow query log (threshold: 1 second)
MySQL/MariaDB tuning:
- •`innodb_buffer_pool_size`: 50–70% of available RAM on dedicated database servers
- •`innodb_log_file_size`: 256MB–1GB for write-heavy sites
- •`query_cache`: Disable on MySQL 8+ (deprecated) or Drupal sites with frequent writes
- •`max_connections`: Set based on actual concurrent connections × 1.5
- •Use MySQLTuner for automated recommendations
Drupal-specific database practices:
- •Run cron regularly (updates search indexes, clears expired caches, runs queue workers)
- •Use `drush cron` via system crontab, not Drupal's built-in poor man's cron (which runs on web requests)
- •For high-traffic sites, move cache tables to a separate Redis/Memcached backend
- •Archive old content revisions if they're not needed
Passing Core Web Vitals on Drupal
Google's Core Web Vitals directly affect search rankings. Drupal's caching architecture gives it a natural LCP advantage.
Fixing LCP (target: ≤ 2.5s):
- 1Verify Internal Page Cache is enabled (anonymous TTFB < 10ms)
- 2Add Varnish or Redis for even faster cache serving
- 3Inline critical CSS per page type
- 4Preload the LCP image (hero banner, featured image) with `fetchpriority="high"`
- 5Aggregate and defer JavaScript
- 6Enable responsive images with appropriate breakpoints
Fixing INP (target: ≤ 200ms):
- 1Remove jQuery if no modules require it (Drupal 10+)
- 2Defer all non-critical JavaScript via library definitions
- 3Reduce DOM complexity (fewer blocks, simpler layouts)
- 4Defer third-party scripts (analytics, chat, social)
- 5Use event delegation instead of individual event listeners
- 6Break Long Tasks with `requestIdleCallback` or `scheduler.yield()`
Fixing CLS (target: ≤ 0.1):
- 1Add `width` and `height` to all `
<img>` tags (responsive image module handles this) - 2Use `font-display: swap` with size-adjusted fallback fonts
- 3Reserve space for dynamic blocks (ads, social widgets)
- 4Avoid JavaScript that inserts content above the fold after initial render
- 5Use `aspect-ratio` CSS for embedded media
Monitoring: PageSpeed Insights for field + lab data. Search Console CWV report weekly. Webprofiler for server-side metrics. Allow 28 days for CrUX data to reflect changes.
Ongoing Speed Monitoring
Drupal sites need structured maintenance as modules update, content grows, and requirements evolve.
Weekly (5 minutes):
- •Check Search Console Core Web Vitals report
- •Quick PageSpeed Insights on homepage and top content page
- •Verify cron is running (Admin → Reports → Status Report)
Monthly (30 minutes):
- •Check cache hit rates (Varnish statistics, Redis INFO)
- •Review recently updated modules for performance regressions
- •Verify database cache tables aren't growing excessively
- •Run PageSpeed Insights on top 5 traffic pages
Quarterly (2–4 hours):
- •Full module audit (profile each, remove unused, update outdated)
- •Database maintenance (OPTIMIZE TABLE, clean watchdog, archive old revisions)
- •Views cache audit (verify all Views have caching enabled)
- •Front-end asset audit (remove unused CSS/JS, update critical CSS)
- •MySQL/MariaDB tuning review (slow query log analysis, buffer adjustments)
- •Competitive speed benchmarking
Performance budgets:
- •TTFB (anonymous cached): < 50ms
- •TTFB (authenticated cached): < 200ms
- •Database queries per page (uncached): < 25
- •Active contrib modules: < 30
- •Total JavaScript: < 200KB
- •Total page weight: < 2MB
- •LCP: ≤ 2.5s, INP: ≤ 200ms, CLS: ≤ 0.1
- •Cache hit rate: > 95%
Thresholds & Benchmarks
| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| TTFB (cached anonymous) | < 50ms | 50–300ms | > 300ms |
| TTFB (cached authenticated) | < 200ms | 200–800ms | > 800ms |
| LCP (Largest Contentful Paint) | ≤ 2.5s | 2.5–4.0s | > 4.0s |
| INP (Interaction to Next Paint) | ≤ 200ms | 200–500ms | > 500ms |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | 0.1–0.25 | > 0.25 |
| Database Queries Per Page | < 25 | 25–80 | > 80 |
| Active Contrib Modules | < 30 | 30–60 | > 60 |
| Cache Hit Rate | > 95% | 80–95% | < 80% |
Key Measurement Tools
Test homepage, article pages, Views listing pages, and taxonomy pages separately. Field data shows real-world Drupal performance.
Performance tab for Long Tasks from contrib modules. Network tab for TTFB analysis and cache header verification. Coverage tab for unused CSS/JS.
Drupal's built-in profiling toolbar (via Devel module). Shows database queries, cache hits/misses, render time, and memory usage per page. Essential for optimization.
Application performance monitoring for deep PHP profiling. Identifies slow functions, database bottlenecks, and memory leaks. Critical for enterprise Drupal sites.
Waterfall analysis with TTFB breakdown. Test cache effectiveness by running first-view (cold) and repeat-view (warm) comparisons.
Core Web Vitals report. Monitor article pages, taxonomy pages, and Views listing pages separately.
Looking for speed help?
Step-by-Step Optimization Guide
Configure all cache layers
Verify Internal Page Cache and Dynamic Page Cache modules are enabled. Install Redis module and configure as cache backend in settings.php. Consider Varnish with Purge module for HTTP-level caching. Add CDN for static assets.
Enable Views caching
Edit every View: Advanced → Caching → Tag-based (Drupal 9+) or Time-based. Switch Views using 'Content' display to 'Fields' display where possible. Add database indexes on filter/sort fields. Paginate all Views with 20+ results.
Audit and clean modules
Enable Webprofiler. Profile each contrib module by disabling one at a time and measuring queries, memory, and render time. Uninstall (not just disable) unused modules. Consolidate overlapping functionality. Use core features over contrib where possible.
Optimize front-end delivery
Enable CSS/JS aggregation. Generate and inline critical CSS per page type. Defer all non-critical JavaScript. Remove jQuery if unused (Drupal 10+). Self-host fonts with font-display: swap. Enable responsive images module.
Tune database
Run OPTIMIZE TABLE on large tables. Add indexes on entity reference fields used in Views. Enable slow query log and fix bottlenecks. Set innodb_buffer_pool_size to 50–70% RAM. Clean watchdog, sessions, and old revisions.
Optimize PHP environment
Use PHP 8.1+ (significant performance improvement). Enable and properly size OPcache. Set JIT compiler. Disable xdebug in production. Configure proper realpath_cache settings. Run cron via system crontab, not poor man's cron.
Pass Core Web Vitals
Verify LCP ≤ 2.5s with full cache stack and critical CSS. Fix INP ≤ 200ms by deferring JS and removing jQuery. Fix CLS ≤ 0.1 with responsive images (width/height) and font-display: swap.
Set up monitoring
Weekly Search Console CWV check. Monthly cache hit rate verification. Quarterly full module audit and database maintenance. Performance budgets: < 50ms TTFB (cached), < 25 queries/page, < 30 contrib modules, > 95% cache hit rate.
Want us to handle these optimizations?
Request an audit for your Drupal site and see results in days, not months.
Drupal in 2026: Updates & Future Trends
Drupal Speed Optimization in 2026 and Beyond
Drupal's performance trajectory is strong:
Drupal 11: Scheduled improvements include automatic critical CSS generation, improved asset pipeline with modern bundling, and enhanced caching defaults out of the box.
Drupal Recipes: The new Recipes system replaces distributions with composable, lightweight configurations. Recipes install only needed functionality, reducing module bloat from the start.
Drupal Starshot: The initiative to make Drupal more accessible includes performance-optimized default configurations, reducing the gap between installation and optimal performance.
Experience Builder: Drupal's next-generation page building tool is being designed with performance in mind — component-level rendering, lazy loading, and minimal JavaScript overhead.
Decoupled/Headless Drupal: JSON:API and GraphQL modules enable headless builds with React/Next.js frontends. Combined with Drupal's content modeling power, this provides maximum performance with maximum content flexibility.
Edge computing: Drupal sites with Varnish or Fastly can leverage edge computing for personalization at the CDN edge — sub-10ms TTFB with dynamic content.
AVIF image support: Drupal's image style system is adding AVIF generation alongside WebP for 50–70% smaller images.
Need help with Drupal speed?
Our team specializes in Drupal performance optimization. Request an audit and see exactly how much faster your site could be.
