Speeding Up MODX Sites with Smart Snippet Caching
Learn how to speed up MODX Revolution sites by caching snippet output and isolating user‑specific logic in thin uncached wrappers.
03 Oct 2025, 13:47 UTC

The problem: every snippet runs on every request
When a MODX Revolution site uses uncached snippet calls ([[!snippet]]) for things like navigation menus or recent‑news lists, the snippet code executes on each page view. That means the same database queries run repeatedly, CPU cycles are wasted, and page load times grow as traffic increases.
Thesis: cache the static part, isolate the dynamic part
MODX already provides a simple caching mechanism: a plain snippet call ([[snippet]]) runs once per cache lifetime and stores its output in the modX cache. By keeping the bulk of the work cached and wrapping only the user‑specific logic in an uncached snippet, you get most of the performance benefit while still delivering personalized content.
How MODX snippet caching works
When MODX encounters [[getNews]] it:
- Builds a cache key from the snippet name and the property set (if any).
- Looks for that key in the active cache context (usually
web). - If found, returns the stored output; if not, executes the snippet, saves the result, and returns it.
An uncached call [[!getNews]] skips the lookup and always runs the snippet, bypassing the cache entirely.
Worked example: a cached news list with a per‑user filter
First, create a snippet named getNews that returns the five most recent published resources:
getOption('limit', $scriptProperties, 5);
$c = $modx->newQuery('modResource');
$c->where(array(
'published' => true,
'deleted' => false,
));
$c->sortby('publishedon', 'DESC');
$c->limit($limit);
$resources = $modx->getCollection('modResource', $c);
$output = '';
foreach ($resources as $res) {
$output .= '' . $res->get('pagetitle') . ' ';
}
return $output;
?>
Place a cached call on your template or chunk:
[[getNews]]
This will run once per cache lifetime (default is until the cache is cleared).
Now add a thin uncached wrapper that removes news items the current user has already read. Assume you store read IDs in a session variable $_SESSION['readNews'] as an array of resource IDs.
runSnippet('getNews', array('limit' => 10)); // get a bit more to allow filtering
if (!is_string($cached)) {
return '';
}
// parse the cached HTML into IDs (simple approach: assume each has data-id)
preg_match_all('/data-id="(\d+)"/', $cached, $matches);
$ids = array_map('intval', $matches[1]);
$read = !empty($_SESSION['readNews']) ? $_SESSION['readNews'] : array();
$filtered = array_diff($ids, $read);
// rebuild list with only unread items
$unreadHtml = '';
foreach ($filtered as $id) {
$res = $modx->getObject('modResource', $id);
if ($res) {
$unreadHtml .= ' ' . $res->get('pagetitle') . ' ';
}
}
return $unreadHtml;
?>
Use the wrapper uncached:
[[!getNewsUser]]
The heavy lifting (fetching resources) stays cached; only the lightweight filtering runs per request, and the output reflects the user’s read state.
Trade‑offs and limitations
- Stale data: If a news resource is unpublished or edited, the cached list will not reflect the change until the cache is cleared.
- Cache granularity: Over‑using uncached wrappers erodes the performance gain; keep them as thin as possible.
- Context awareness: In multi‑context setups (e.g.,
webandmgr), you must clear the appropriate context; otherwise changes won’t appear in the frontend.
Practical verification: after installing the snippet, enable caching (System Settings → cache_enabled = true). Load a page with [[getNews]] several times, then check the X‑PDO log ([[++cache_path]]/logs/error.log) – you should see the snippet executed only once. Publish a new resource, then manually clear the cache (Site → Clear Cache) and reload; the new item appears.
Actionable closing
Audit your site’s snippets: mark those that return the same data for every visitor as cached ([[snippet]]). Move any per‑user or real‑time logic into small uncached wrappers that operate on the cached output. Finally, create a simple plugin on the OnDocFormSave event that calls $modx->cacheManager->clearCache() to keep your cached lists fresh without sacrificing speed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.