Browser caching strategies
Understanding cache http header management in Jahia
Overview
The Jahia Client Cache Control module manages HTTP Cache-Control headers for all resources served by Jahia. It ensures that browsers, proxies, and CDNs receive cache directives consistent with the nature and content type of each resource.
The module works by:
- Intercepting HTTP requests at the earliest stage (servlet filter level)
- Evaluating request URL patterns against configured rules
- Applying appropriate Cache-Control headers based on the resource type and caching strategy
- Fine-tuning cache policies during page rendering (for dynamic content)
- Enforcing the final header in the HTTP response
Cache Control Strategies
The module defines five cache control strategies (templates), each with a specific use case:
Cache Strategies Comparison Table
| Strategy | Browser Cache Duration | CDN/Proxy Cache Duration | Jahia Content Type | Examples | Jahia Internal Resource | Best For |
|---|---|---|---|---|---|---|
| Immutable | 1 month | 1 month | Static Assets with versioned URLs | Generated resources, hashed JS/CSS files | Generated Resource Servlet | Content that never changes |
| Public | 1 second (check every request) | ~1 minute | HTML Pages (live mode) | Published pages, public content | RenderChain (page rendering) | Frequently updated public pages |
| Public-Medium | 1 second (check every request) | ~10 minutes | Static Files, Media, Modules | Media library files, module JS/CSS, downloadable documents | FileServlet, Repository Servlet | Static content, assets |
| Custom | 1 second (check every request) | Variable (component-based) | Mixed Components | Pages with diverse content freshness needs | RenderChain (with component properties) | Complex pages with per-component TTL |
| Private | 0 seconds (never cache) | 0 seconds (never cache) | Personalized/Admin Content | User profiles, admin panels, sensitive data | RenderChain (private fragments) | User-specific or sensitive content |
1. Immutable Strategy
Default Header: public, max-age=##immutable.ttl##, s-maxage=##immutable.ttl##, stale-while-revalidate=15, immutable
Default TTL: 1 month (2,678,400 seconds)
Purpose: For resources with unique, non-changing URLs
Characteristics:
- Clients cache forever (until cache expiration)
- CDNs/proxies cache forever
- No revalidation required from the client
- Maximum performance optimization
Used for:
- Generated resources with unique filenames
- Static assets with versioned URLs
- Images, CSS, JavaScript with content hashes in filenames
2. Public-Medium Strategy
Default Header: public, must-revalidate, max-age=1, s-maxage=##medium.ttl##, stale-while-revalidate=15
Default TTL: 10 minutes (600 seconds)
Purpose: For static content that changes infrequently
Characteristics:
- Browsers: check for updates on each request (max-age=1s)
- CDNs/proxies: cache for ~10 minutes
- Users may see stale content for up to 10 minutes after publication
- Balance between performance and content freshness
Used for:
- Static files (CSS, JavaScript without versioning)
- Images in media library
- Downloadable documents
- Module resources
3. Public Strategy (Short-term)
Default Header: public, must-revalidate, max-age=1, s-maxage=##short.ttl##, stale-while-revalidate=15
Default TTL: 1 minute (60 seconds)
Purpose: For frequently updated public content
Characteristics:
- Browsers: check for updates on each request (max-age=1s)
- CDNs/proxies: cache for ~1 minute
- Users see updates within ~1 minute
- Good balance for most web content
Used for:
- HTML pages in live mode
- Dynamic content that changes regularly
- Pages without personal information
4. Custom Strategy (user-defined term)
Default Header: public, must-revalidate, max-age=1, s-maxage=%%jahiaClientCacheCustomTTL%%, stale-while-revalidate=15
TTL: reflect the internal Jahia cache TTL value set for the component (via cache.expiration property)
Purpose: For dyanmic content with developper defined expiration times
Characteristics:
- Browsers: check for updates on each request (max-age=1s)
- CDNs/proxies: cache duration set dynamically based on component properties
- Allows per-component cache duration configuration
- Uses Jahia's
cache.expirationtemplate property
Used for:
- Pages with mixed components having different expiration times
- Content with component-specific cache requirements
5. Private Strategy (Most Restrictive)
Default Header: private, no-cache, no-store, must-revalidate, proxy-revalidate, max-age=0
Purpose: For personalized, user-specific, or sensitive content
Characteristics:
- Browsers: no caching (max-age=0)
- CDNs/proxies: no caching
- No revalidation - must fetch fresh copy
- Most restrictive caching policy
- Each user gets personalized content
Used for:
- Admin pages
- User accounts and profiles
- Personalized content
- Pages with user-specific fragments
- All mutating operations (POST, DELETE, PATCH)
How Cache Strategies are Applied
A predefined ruleset ensure that correct cache strategies are applied to Jahia content URLs. For common usage, there is no need to change the default ruleset as it covers all Jahia internal URLs with appropriate strategies. The ruleset is defined in a YAML file and can be customized by modules.
Rule Matching Process
The module uses an ordered priority system to apply cache strategies:
- HTTP request arrives at the web server
- ClientCacheFilter (servlet filter) intercepts the request
- Rules are evaluated in priority order (lower priority number = evaluated first)
- First matching rule is applied based on:
- HTTP method (GET, HEAD, POST, etc.)
- Request URI (matched against regular expressions)
- Cache-Control header is set using either:
- A template reference (e.g.,
template:public) - A literal header value (e.g.,
no-store,no-cache,must-revalidate)
- A template reference (e.g.,
Default Rule Application
The default ruleset applies the following strategies:
| Priority | HTTP Methods | URL Pattern | Strategy | Purpose |
|---|---|---|---|---|
| 1 | GET, HEAD | /cms/render/live/.* | public | Published pages in live mode |
| 2 | GET, HEAD | /cms/.* | private | CMS administrative URLs |
| 3 | GET, HEAD | /welcome.* | private | Welcome page (requires authentication check) |
| 4 | GET, HEAD | /start | private | Start page |
| 5 | GET, HEAD | /validateTicket | private | Ticket validation |
| 6 | GET, HEAD | /administration.* | private | Administration pages |
| 7 | GET, HEAD | /files/.* | public-medium | Media library files |
| 8 | GET, HEAD | /repository/.* | public-medium | Repository assets |
| 9 | GET, HEAD | /modules/.* | public-medium | Module resources |
| 10 | GET, HEAD | /engines/.*\.jsp | private | JSP engines |
| 11 | GET, HEAD | /tools(/.*)? | private | Jahia tools |
| 12 | GET, HEAD | /gwt/.*\.nocache\..* | private | GWT non-cacheable resources |
| 13 | GET, HEAD | /generated-resources/.* | immutable | Generated resources (hashed URLs) |
| 14 | POST, DELETE, PATCH | .* | private | All mutations (state-changing operations) |
| 15 | GET, HEAD | .* | public | Fallback for all other content |
Changing the configuration
Global Client Cache Control configuration:
The configuration can be changed by using Jahia tools => OSGI console => configuration => org.jahia.bundles.cache.client Thus you can change global configuration like the default TTL : short.ttl, medium.ttl or immutable.ttl
Adding specific rules:
Any module can also provide specific ruleset by creating a file in src/main/resources/META-INF/configurations/org.jahia.bundles.cache.client.ruleset-<modulename>.yml with the same format as the default ruleset. The rules will be merged and sorted by priority with the default ruleset. Priority is a float number, the lower the priority number, the earlier the rule is evaluated. This allows you to insert rules at specific positions in the evaluation order without having to change the default ruleset (which is not recommended).
Request Flow and Cache Header Application
HTTP Request
↓
[ClientCacheFilter - Servlet Filter]
├─ Wraps response to intercept header modifications
├─ Evaluates URL pattern against rules (in priority order)
├─ Pre-sets Cache-Control header based on matched rule
└─ (STRICT mode: locks header from further modification)
↓
[Request Processing]
├─ Static Servlet Processing (files, assets)
│ └─ Header pre-set by filter (may be locked in STRICT mode)
├─ RenderChain Processing (page rendering)
│ ├─ Default "public" policy assigned to RenderContext
│ ├─ Fragment evaluation with CacheKeyPartGenerator
│ ├─ Fragment policies merged (may downgrade to "private")
│ └─ ClientCacheRenderFilter applies final policy
└─
[Response Headers Set]
├─ Filter pre-set header (may be overridden in ALLOW_OVERRIDES mode)
├─ Or final header from RenderChain processing
└─
[Client/CDN Receives Response]
└─ Cache-Control header dictates browser and CDN behaviorKey Takeaways
- Immutable: Used for versioned static resources that never change
- Public/Public-Medium: Used for public, non-personalized content
- Custom: Used when different components on a page need different cache durations
- Private: Used for personalized or sensitive content
- Browser vs CDN: Browsers typically check on every request (max-age=1s), while CDNs cache longer (s-maxage=60_to_600s)
- Jahia Pages: Dynamic pages render through the RenderChain and may be downgraded from public to private if they contain user-specific content
Understanding and Tweaking cache strategies to specific use cases
Internal Architecture and Request Flow
This section explains how the Jahia Client Cache Control module works internally and how you can customize it for your specific needs.
Step 1: Initial Request Interception (ClientCacheFilter)
When an HTTP request arrives at Jahia:
- ClientCacheFilter (a servlet filter) intercepts all requests (
pattern=/*) - The filter wraps the response in a ClientCacheResponseWrapper to control header modifications
- The filter looks up matching rules based on HTTP method and request URI
- If a rule matches, a Cache-Control header is pre-set on the response
Filter Behavior:
- Operates in two modes:
- ALLOW_OVERRIDES mode (default): Components can override the pre-set header
- STRICT mode: Header is locked; any override attempt logs an error
- Preserves original request URI for debugging
Step 2: Request Processing
For Static Content (Servlets)
For static files, assets, and servlets:
- The pre-set header from ClientCacheFilter is used
- In STRICT mode, the header cannot be changed
- In ALLOW_OVERRIDES mode, servlets can override it if needed
For Dynamic Content (RenderChain - Page Rendering)
For HTML page rendering through Jahia's RenderChain:
- RenderContext Creation:
- A new RenderContext is created for the page request
- A default "public" ClientCachePolicy is assigned
- This represents the initial assumption that the page is cacheable publicly
- Fragment Rendering and Cache Key Analysis:
- As each fragment (content component) is rendered:
- Jahia's AggregateCacheFilter evaluates the fragment
- All CacheKeyPartGenerators registered for that fragment are called
- Each generator can signal if the fragment is user-specific or personalized
- This determines the ClientCacheFragmentPolicy for that fragment:
- Public (default)
- Private (if any generator indicates personalization)
- As each fragment (content component) is rendered:
- Fragment Policy Merging:
- Policies from all rendered fragments are merged into the global RenderContext policy
- Rules: If any fragment is private, the page becomes private
- Example:
- Header (public) + Navigation Menu (public) + User Profile (private) = Page becomes private
- Final Policy Application (ClientCacheRenderFilter):
- At the end of the RenderChain, ClientCacheRenderFilter reads the final policy
- It calls
ClientCacheService.getCacheControlHeader()with:- The policy level (public, public-medium, custom, private)
- Custom TTL parameter if needed
- Sets a special
Force-Cache-Controlheader that overrides the initial pre-set header - This ensures RenderChain policies take precedence
The Role of ClientCachePolicyContributor and CacheKeyPartGenerators
CacheKeyPartGenerators are the key mechanism that determines how to cache a page fragment. CacheKeyPartGenerators implement the ClientCachePolicyContributor interface that is able to influence the cache policy of a page fragment during rendering.
What is a ClientCachePolicyContributor?
A ClientCachePolicyContributor is a plugin that analyzes a fragment and returns a ClientCachePolicy according to the fragment content.
By default, most of the fragments are considered public. However, if a fragment detects user-specific content or any information that may prevent the global page to be cached, it can return a private policy, which will downgrade the global page policy to private.
How They Work
- During fragment rendering, Jahia invokes all registered CacheKeyPartGenerators
- Each generator is called and the ClientCachePolicyContributor.getClientCachePolicy is evaluated to gather a ClientCachePolicy for that fragment
- The policies from all generators are merged to determine the final policy for the page
- If any generator returns a private policy, the entire page becomes private; otherwise it remains public
- If the fragment contains a specific cache strategy (e.g., cache.properties with a specific TTL), it can also influence the final cache header applied to the page to propagate the specific ttl.
ClientCachePolicyContributor that requires a Private policy in Jahia
- AclCacheKeyPartGenerator: As the same fragment can be rendered with different content based on user permissions, it enforces a private policy
- AjaxCacheKeyPartGenerator: Most of the content retrieved by ajax is not cacheable as they are often user-specific or personalized, thus it enforces a private policy
All other default generators enforce a public cache policy.
Debugging: Which Generator Makes the Page Private?
To understand why a page is private, enable DEBUG logging for:
org.jahia.services.render.filter.cache.AggregateCacheFilterHow to Enable Debug Logging (via Jahia Tools):
- Log in to Jahia as administrator
- Go to Tools > Configuration > Logging
- Add new loggers:
- Class/Package:
org.jahia.services.render.filter.cache.AggregateCacheFilter - Level:
DEBUG - Class/Package:
org.jahia.bundles.cache.client.render.ClientCacheRenderFilter - Level:
DEBUG
- Class/Package:
Log Output Example:
DEBUG [AggregateCacheFilter] Determining client cache policy for fragment with key @@key@@
DEBUG [AggregateCacheFilter] Fragment with key @@key@@ requires 'private' client cache policy (cache.private)
(...)
DEBUG [AggregateCacheFilter] Determining client cache policy for fragment with key @@key@@
DEBUG [AggregateCacheFilter] Fragment with key @@key@@ requires 'public' client cache policy (computed)Configuration and Customization
Understanding the YAML Ruleset
The default ruleset is located in:
client-cache-control-bundle/src/main/resources/META-INF/configurations/
org.jahia.bundles.cache.client.ruleset-default.ymlRuleset Format:
Each rule has four segments separated by semicolons (;):
priority;methods;urlRegex;headerSpecExample:
- "1;GET|HEAD;(?:/[^/]+)?/cms/render/live/.*;template:public"
│ │ │ └─ Header specification
│ │ └─ URL regular expression
│ └─ HTTP methods (pipe-separated)
└─ Priority (float, lower = evaluated first)Header Specifications:
- Template reference:
template:public,template:private,template:immutable, etc. - Literal value:
no-store,no-cache,must-revalidate(raw Cache-Control header)
Custom Module Rules
To add cache rules for your module:
Create a ruleset file in your module at:
src/main/resources/META-INF/configurations/ org.jahia.bundles.cache.client.ruleset-<yourmodulename>.yml- Define your rules following the same format as the default ruleset
- Use appropriate priorities:
- Rules are merged with default rules and sorted by priority
- Use floating-point priorities to insert rules at specific positions
- Lower priority numbers are evaluated first
Example: Immutable Assets
If your module contains Angular JS code with unique filenames:
name: "MyModule Ruleset" description: "Custom cache rules for MyModule" rules: - "8.99;GET|HEAD;(?:/[^/]+)?/modules/mymodule/js/uniquefilename.js;template:immutable"Priority Placement:
- Default rule for
/modules/.*has priority 9 - Custom rule uses priority 8.99 (just before)
- This ensures your rule is matched first
- Default rule for
Custom TTL Configuration
You can customize the default TTL values for cache strategies:
- Via Jahia Tools:
- Go to Tools > Configuration > OSGi Configuration
- Find PID:
org.jahia.bundles.cache.client - Adjust:
short_ttl(default: 60 seconds)medium_ttl(default: 600 seconds = 10 minutes)immutable_ttl(default: 2,678,400 seconds = 1 month)
- Via Configuration Files:
- Create
org.jahia.bundles.cache.client.cfgin Jahia's deployment directory Set properties:
short_ttl=60 medium_ttl=600 immutable_ttl=2678400
- Create
Custom Cache-Control Headers
You can customize the actual Cache-Control header values for each template:
- Via Jahia Tools:
- Go to Tools > Configuration > OSGi Configuration
- Find PID:
org.jahia.bundles.cache.client - Customize:
cache_header_template_privatecache_header_template_publiccache_header_template_public_mediumcache_header_template_customcache_header_template_immutable
- Placeholder Replacement:
##short.ttl##→ replaced withshort_ttlvalue##medium.ttl##→ replaced withmedium_ttlvalue##immutable.ttl##→ replaced withimmutable_ttlvalue%%jahiaClientCacheCustomTTL%%→ replaced with custom TTL from component properties
Example:
public, must-revalidate, max-age=1, s-maxage=##short.ttl##, stale-while-revalidate=15, immutableStrict Mode vs Allow Overrides Mode
The module operates in two modes:
ALLOW_OVERRIDES Mode (Default)
- Behavior:
- ClientCacheFilter pre-sets a header based on rules
- Other components can override this header during request processing
- In RenderChain, ClientCacheRenderFilter overrides with the final policy
- Any override is logged at DEBUG level
- Use Case: Usage of modules that do not use the latest ruleset and that overrides cache headers directly (NOT RECOMMENDED)
- Enable via Jahia Tools:
- Go to Tools > Configuration > OSGi Configuration
- Find PID:
org.jahia.bundles.cache.client - Set
modetooverrides
STRICT Mode
- Behavior:
- ClientCacheFilter pre-sets a header and locks the response
- The header cannot be modified by any component
- Any attempt to override the header logs an ERROR message
- ClientCacheRenderFilter uses the special
Force-Cache-Controlheader that can bypass this lock (RESERVED FOR RENDERCHAIN FINAL POLICY ONLY)
- Use Case: Well-known environments where you want to enforce strict cache policies and prevent any accidental overrides or dublin values
- Enable via Jahia Tools:
- Go to Tools > Configuration > OSGi Configuration
- Find PID:
org.jahia.bundles.cache.client - Set
modetostrict
Common Customization Scenarios
Scenario 1: Your Module Has Unique Versioned Assets
Problem: You want /modules/mymodule/js/uniquefilename.v123.js to be cached forever (immutable).
Solution:
Create
src/main/resources/META-INF/configurations/org.jahia.bundles.cache.client.ruleset-mymodule.yml:name: "MyModule Assets" description: "Immutable js assets for MyModule" rules: - "8.42;GET|HEAD;(?:/[^/]+)?/modules/mymodule/js/.*\.v[0-9]+\.js;template:immutable"- These rules will be inserted before the default
/modules/.*rule (priority 9)
Scenario 2: Your Module Has Personalized Content Pages
Problem: Your module includes specific CacheKeyPartGenerator to ensure that internal Jahia cache is aware of your specific content.
Solution:
- Overrides the getClientCachePolicy method of the ClientCachePolicyContributor interface to return a private policy when the content is personalized
- The cache policy will automatically become
PRIVATEwhen personalized fragments are rendered - Verify with DEBUG logging on
ClientCachePolicyContributor
Monitoring and Troubleshooting
Enable Debug Logging
Enable DEBUG logging to see how the module processes requests:
- For ClientCacheFilter (initial rule matching):
- Logger:
org.jahia.bundles.cache.client.filter.ClientCacheFilter - Shows rule matching and header pre-setting
- Logger:
- For ClientCacheRenderFilter (final policy application):
- Logger:
org.jahia.bundles.cache.client.render.ClientCacheRenderFilter - Shows final policy and header application
- Logger:
- For Cache Policy Decisions (which generator made it private):
- Logger:
org.jahia.services.render.filter.cache.AggregateCacheFilter - Shows which fragments are private and why
- Logger:
How to Enable (via Jahia Tools):
- Go to Tools > Configuration > Logging
- Click "Add logger"
- Enter the class name (e.g.,
org.jahia.bundles.cache.client.filter.ClientCacheFilter) - Set level to
DEBUG - Save
Reference Sample Modules
Jahia provides sample modules demonstrating common use cases:
- Cache Control Sample:
- URL: https://github.com/Jahia/OSGI-modules-samples/tree/master/cache-control-sample
- Demonstrates: Custom rules, template usage, component-level TTL configuration
- Cache Key Part Generator Samples:
- URL: https://github.com/Jahia/OSGI-modules-samples/tree/master/cache-key-part-generator-samples
- Demonstrates: Creating custom generators to detect personalization
- Explains: How to make pages private based on custom logic
Recommended: Study these samples to understand advanced customization patterns.
Key Architecture Components
| Component | Purpose | File |
|---|---|---|
| ClientCacheFilter | Servlet filter that pre-sets headers based on URL rules | ClientCacheFilter.java |
| ClientCacheResponseWrapper | Wraps response to control header modifications | ClientCacheResponseWrapper.java |
| ClientCacheServiceImpl | Core service managing rules, templates, and configuration | ClientCacheServiceImpl.java |
| ClientCacheRenderFilter | RenderChain filter that applies final policy from fragments | ClientCacheRenderFilter.java |
| CacheKeyPartGenerator | Jahia plugin detecting personalized fragments | Jahia core (render-service) |
| ClientCachePolicyContributor | Jahia core component merging fragment policies | Jahia core (render-service) |
| AggregateCacheFilter | Jahia's fragment cache filter invoking generators | Jahia core (render-service) |
Best Practices
- Start from the default ruleset: Don't modify the default rules unless absolutely necessary. Instead, add custom rules for your module.
- Use floating-point priorities: When inserting custom rules, use floating-point priorities (e.g., 8.99, 8.501) to avoid collisions.
- Test with DEBUG logging: Always enable DEBUG logging for
ClientCacheFilterandClientCacheRenderFilterduring development. - Understand your fragments: Use DEBUG logging on
AggregateCacheFilterto understand which fragments trigger private policies. - Document custom rules: Document why you added custom rules so future developers understand the intent.
- Use templates, not literals: Prefer
template:publicover literal headers, so TTL changes apply automatically. - Use immutable only when appropriate: Only use the
immutabletemplate for resources with truly unique URLs.
