Caching Lessons from Modern DataTools: Keeping Pages Fast and Fresh
Modern DataTools uses page caching, targeted invalidation, and shared data caches to reduce repeated work without allowing important information to remain outdated - in the article we cover our key lessons for these mechanisms.
EB
Egor Burlakov
••6 min read
While building Modern DataTools, I initially thought performance would mostly be a hosting concern. It turned out to be a product decision.
As the site grew, the same information began appearing across reviews, comparisons, pricing guides, alternatives pages, and category rankings. A single verified update could affect several pages, while readers, search crawlers, link prefetching, and monitoring multiplied the number of requests.
Rendering every page from scratch keeps information fresh but repeats expensive work. Caching everything indefinitely improves speed but allows outdated information to linger.
The real question is not whether to cache. It is what to cache, how long the result may remain unchanged, and what should trigger a refresh.
Our caching architecture evolved in layers. Each improvement removed one source of repeated work but exposed the next one.
Three lessons from building the caching system
Every cached page needs a path back to freshness.
Invalidate dependencies, not the whole catalogue.
Cache reusable data, not only completed pages.
These lessons are connected by a broader principle:
Freshness is not a universal technical setting. It is a product promise defined separately for each type of information.
A category count can be a few minutes old without causing harm. A verified pricing correction should appear quickly. Account permissions must be correct when they are checked.
The right caching strategy depends on which of these promises the product needs to keep.
Lesson 1: Every cached page needs a path back to freshness
The problem
Consider a comparison page for two widely used data platforms. Most of its editorial analysis, feature descriptions, and related-tool recommendations change slowly. Pricing, announcements, and corrections may change more quickly.
Rebuilding the page for every reader would repeat expensive work. Keeping one permanent static version would leave updates invisible until the page was explicitly rebuilt.
What changed
We gave every cached page two paths back to freshness.
The first is time-based revalidation. A page remains cached for a defined period—for example, 15 minutes. Once that period expires, a later request can trigger regeneration, and subsequent readers receive the updated version.
The second is change-triggered invalidation. When an editor verifies new pricing or corrects a product description, the application can mark the cached page as outdated immediately rather than waiting for the normal refresh interval.
Visual 1: A cached page can return to freshness through either scheduled revalidation or a verified change event.
In Next.js, this pattern combines Incremental Static Regeneration with explicit revalidation: serve a cached page quickly, then regenerate it when its refresh interval expires or the application invalidates it after a verified change.
The trade-off
A cached page may be briefly stale. That is acceptable when the delay is intentional, bounded, and appropriate for the information.
A related-tools section may tolerate a short delay. A material pricing correction may require immediate invalidation.
The lesson: caching is safe only when every cached result has an explicit path back to freshness.
This solved freshness for an individual page. The next problem was determining what to refresh when one verified change affected many pages across the catalogue.
Lesson 2: Invalidate dependencies, not the whole catalogue
The problem
Suppose a vendor changes its pricing. The update may affect its review, pricing guide, selected comparisons, alternatives pages, and category content.
Rebuilding the entire catalogue would keep everything current, but a change affecting five pages could regenerate hundreds of unrelated ones. Waiting for each page’s normal refresh interval would avoid that work, but important updates could remain invisible for too long.
The refresh scope should follow the change itself.
What changed
We started treating invalidation as a dependency problem.
After a verified update, the application identifies the pages and datasets that depend on the changed information, marks only those results as outdated, and leaves unrelated content cached.
Visual 2: The controlled blast radius of one verified update across the Modern DataTools catalogue.
Different changes have different dependency paths. A pricing update may affect a guide, selected comparisons, and category context. A new integration may affect the review, integration directory, comparison tables, and recommendations. A renamed product may also affect navigation, URLs, and redirects.
Affected pages do not always need to be rebuilt immediately. They can be marked as outdated and regenerated when readers next request them, keeping the work proportional both to the scope of the change and to actual demand.
The trade-off
Targeted invalidation depends on an accurate dependency model.
If a relationship is missing, an affected page may remain stale. If the model is too broad, the system returns to rebuilding more content than necessary.
The lesson: invalidate according to the scope of the change, not according to the size of the catalogue.
This stopped us from rebuilding unrelated pages. But when an affected page did need to be regenerated, several application instances could still request the same underlying data.
Page caching had solved repeated responses—not repeated questions.
Lesson 3: Cache reusable data, not only completed pages
The problem
Page caching avoids rendering the same response repeatedly. But when a page does need to be rebuilt, the application may still request the same underlying data again.
For Modern DataTools, that includes published tools, category counts, navigation data, summaries, and comparison metadata.
An in-memory cache can reuse those results within one application instance. But when several instances or regions handle requests, each may still load the same data independently.
A page cache reuses the completed response. It does not automatically prevent repeated database work during regeneration
What changed
We introduced a shared cache for public data that is stable and identical for every reader.
When a complete page is available, readers receive it from the page cache. When the page must be rebuilt, application instances reuse common data from the shared cache before querying the database.
Visual 3: Page caching and shared data caching remove repeated work at different layers.
The two caches operate at different scopes. A comparison page is one completed response. The published-tool list may be reused across comparisons, category pages, navigation, alternatives pages, and publishing workflows.
Caching that shared result once reduces database queries, data transfer, serialisation, and repeated application processing across many routes.
The trade-off
A shared cache is safe only when the result can genuinely be shared.
Public catalogue data is a strong candidate because every reader should receive the same answer. Personalised data, permissions, saved preferences, and protected admin information require user-scoped or dynamic handling.
The lesson: page caches reuse completed answers; shared caches reuse the ingredients used to produce them.
The goal is less repeated work, not less care
A good caching system does not simply store more information or keep results for longer.
It decides:
what work can be reused;
who can safely reuse it;
which changes make the result invalid;
how quickly an updated result must appear.
For Modern DataTools, that required several layers working together:
page caching to reuse completed responses;
time-based revalidation to place a bound on staleness;
dependency-aware invalidation to refresh only affected content;
shared data caching to avoid repeated database and application work;
freshness promises to determine when reuse remains acceptable.
Each layer solves a different form of repeated work. Together, they make the system faster without weakening the accuracy readers depend on.
The objective is therefore not the highest possible cache-hit rate. It is to perform expensive work once, reuse the result safely, and repeat the work only when the underlying information—or the freshness promise—requires it.
Engineering and Science Leader with experience building scalable data infrastructure, data pipelines and science applications. Sharing insights about data tools, architecture patterns, and best practices.
Explore Further
Dive deeper into the tools and categories mentioned in this article.