Template cache with client-side placeholder vs. WireCache fragments for mostly-static ProcessWire pages
0 reputation · 17 Sept 2024, 10:23 UTC
0 reputation · 17 Sept 2024, 10:23 UTC
Assume ProcessWire 3.x with MySQL. A set of content pages is roughly 95% identical for all visitors — long article bodies built from several field queries and a couple of expensive selectors — but each page also contains one small personalized fragment, such as a greeting or a per-user badge.
Two documented approaches seem to compete here. Template-level page caching (enabled per template in the admin) caches the entire rendered output for a TTL and can be cleared on page save, which gives the highest hit rate but cannot hold per-user output. The alternative is to leave the template uncached and wrap only the expensive fragments in $cache->get()/save() (WireCache), letting the personalized part render dynamically on every request.
The trade-off I cannot resolve: with template cache, the personalized fragment would need to be rendered as a placeholder and filled in client-side, adding JavaScript and an extra request. With fragment caching only, the base page markup and its queries are rebuilt on every hit even though they rarely change, and TTL-only expiry risks serving stale fragments after edits.
Which approach is the better fit for this profile, and why?
Is there a documented way to combine template caching with a per-user fragment without risking one visitor's personalized content being served to another?
How should invalidation be handled so that editing a page refreshes both the static output and any long-TTL WireCache fragments that depend on it?
33275 reputation · 17 Sept 2024, 14:50 UTC
For a page that is 95 % static and 5 % per‑user, the most reliable pattern is:
$cache->get('user_greeting_' . $user->id) to cache the per‑user data.Template cache stores the *entire* rendered output. If you put a $cache->get() inside a cached template, the result is cached as part of the page, so the personalized fragment would be shared across users. The only way to avoid that is to keep the fragment out of the cached output.
The client‑side placeholder approach keeps the heavy work out of the user’s browser and avoids any risk of leaking data between sessions. The AJAX request is cheap because the fragment itself is small, and the per‑user cache key guarantees isolation.
ProcessWire does not provide a built‑in way to mix template caching with per‑user output. The safe pattern is:
<div id="user-greeting" data-user="{$user->id}"></div> placeholder./wire/api/users/greeting (or similar) passing the user ID.$cache->get('user_greeting_' . $user->id).Because the placeholder is not part of the cached HTML, each user gets a fresh AJAX call and the server can safely cache the fragment per user.
When a page is edited you need to clear two things:
$page->watch('cache', function($event) { $cache->clear('user_greeting_*'); }); or to add a custom hook in wire/modules/ProcessPageSave.php that calls $cache->clear('user_greeting_' . $user->id) for all affected users.Because the per‑user fragment is short‑lived (TTL of a few minutes is usually enough), stale data is unlikely to be served after an edit.
If you can tolerate rendering the static part on every request (e.g., the selectors are cheap or the user base is small), you could wrap the entire template in $cache->get() and add a per‑user key. However, this defeats the purpose of template cache and will increase CPU usage.
Do you have any requirement that the personalized fragment must be server‑rendered in the initial HTML (e.g., for SEO or no‑JS scenarios)? If yes, you would need a different approach such as a per‑user cache key for the whole template, which is not supported out of the box.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.