Caching & Performance

TableCrafter renders every table server-side and serves it from a Stale-While-Revalidate (SWR) transient cache, delivering sub-100ms loads from slow third-party APIs while keeping your WordPress database free of stored table data.

SWR CachingTransientsServer-Side RenderingZero DB FootprintWP-CLI

How caching works

TableCrafter never stores your remote data as custom database rows. Instead it uses the WordPress Transients API as a temporary, self-expiring cache layer. Two distinct caches work together on every shortcode render:

Both are plain WordPress transients, so they transparently use your object cache (Redis, Memcached) when one is installed, and fall back to the options table otherwise. The default time-to-live for both caches is one hour (HOUR_IN_SECONDS).

Stale-While-Revalidate (SWR)

SWR is what makes tables feel instant even when the underlying API is slow or temporarily down. On a cached request, TableCrafter serves the existing HTML immediately and only schedules a background refresh if the cache has aged past the stale threshold of 300 seconds (5 minutes).

  1. First render (synchronous): No cache exists, so TableCrafter fetches the source, renders the HTML server-side, and stores both the markup and the data snapshot for one hour.
  2. Fresh cache hit (< 5 min old): The stored HTML is returned instantly. No network request, no revalidation.
  3. Stale cache hit (> 5 min old): The stored (stale) HTML is still returned instantly to the visitor, and a one-off background job (tc_refresh_single_source) is scheduled to silently re-fetch and re-render for the next visitor.

The result is that no visitor ever waits on a third-party API. The slow path happens in the background, off the critical render path.

ℹ️

The stale threshold (5 minutes) is independent of the transient TTL (1 hour). The 5-minute window decides when to revalidate in the background; the 1-hour TTL decides when the cache is discarded entirely and a fresh synchronous fetch is required.

Cache keys and collision safety

The HTML cache key is an MD5 hash built from every attribute that can change the rendered output — source, include, exclude, search, filters, export, per_page, and sort — plus the current plugin version. This means two shortcodes pointing at the same URL but with different columns or toggles never collide on the same cache entry.

Because TABLECRAFTER_VERSION (currently 3.5.6) is part of every key, upgrading the plugin automatically invalidates all stale caches without any manual flush.

Server-side rendering for SEO

Every table is rendered to a complete HTML <table> on the server before the page is sent to the browser. Search engine crawlers — Google, Bing, and AI crawlers alike — see the full data in the source markup, not an empty container waiting on JavaScript.

The rendered markup is emitted inside the container with a data-ssr="true" flag so the front-end library knows the content is already present:

<div class="tablecrafter-container" data-source="..." data-ssr="true">
  <!-- fully rendered, crawlable <table> markup -->
  <script type="application/json" class="tc-initial-data">[ ... ]</script>
</div>

Because the table is real HTML, it benefits from clean semantic structure (scope="col" headers, ARIA labels) that supports rich snippets and strong Core Web Vitals scores.

Zero-latency hydration

Alongside the server-rendered table, TableCrafter embeds the row data as an inline JSON payload in a script.tc-initial-data tag. When the front-end library boots, it "hydrates" the existing markup using this embedded data instead of issuing a second network request. This eliminates the classic double-fetch problem: the table becomes interactive (search, sort, filter) instantly, with no flash of unstyled content and no redundant API call from the browser.

Zero database footprint

Unlike table plugins that import your data into custom tables, TableCrafter stores nothing permanent in your database. The only persistent footprint is:

When the plugin is deleted, uninstall.php removes every transient (data, HTML, export, and rate-limit caches plus their timeout rows), unschedules all cron events, deletes plugin options, and clears temporary export files — leaving no orphaned data behind.

This keeps your wp_options and database lean. There are no migrations, no bloat, and no schema changes when you add or remove tables.

Cache warming & background refresh

TableCrafter proactively keeps caches warm so the first visitor after expiry rarely pays the fetch cost. Two mechanisms handle this:

MechanismTriggerBehavior
Hourly warmerWP-Cron event tc_refresher_cronRe-fetches every tracked source URL and refreshes its data transient.
On-demand revalidationSingle event tc_refresh_single_sourceScheduled by SWR when a stale HTML cache is served; re-renders that one table in the background.

Any source successfully fetched through the shortcode renderer or the AJAX proxy is automatically added to the tracked-URL list (capped at the 50 most recent), so warming targets exactly the tables your site actually uses.

ℹ️

WP-Cron only fires when your site receives traffic. On low-traffic sites, wire tc_refresher_cron to a real system cron for predictable hourly warming.

Large datasets: virtual scrolling & lazy loading

For big tables, the TC_Performance_Optimizer class avoids rendering thousands of DOM nodes at once. Datasets larger than 500 rows automatically switch to virtual scrolling, where only a small window of rows is kept in the DOM:

SettingValuePurpose
Virtual scroll threshold500 rowsAbove this size, virtual scrolling activates automatically.
Rows rendered50Number of visible rows kept in the viewport.
Buffer rows10Extra rows above/below the viewport for smooth scrolling.

Additional row windows are streamed on demand through the tc_virtual_scroll_data AJAX endpoint, which is nonce-protected and serves from the cached full dataset. Image cells are lazy-loaded behind a lightweight SVG placeholder, and very long text is rendered as a truncated preview, both reducing initial page weight.

Managing the cache

WP-CLI

The plugin registers a tablecrafter WP-CLI command with two subcommands for cache management:

# Purge every TableCrafter transient (data, HTML, export caches)
wp tablecrafter clear-cache

# Re-fetch and refresh the cache for all tracked source URLs
wp tablecrafter warm-cache

clear-cache reports the number of transients removed; warm-cache runs the same routine as the hourly cron immediately.

Programmatic control

The TC_Cache singleton exposes the underlying cache operations for developers who need finer control — for example, clearing the cache for a single source after a known data change:

$cache = TC_Cache::get_instance();

// Invalidate one source URL's data cache
$cache->clear_source( 'https://api.example.com/prices.json' );

// Purge everything
$cache->clear_all();
⚠️

For frequently changing data, prefer the front-end auto_refresh attribute over shortening cache TTLs. SWR is designed to absorb slow APIs in the background; aggressive cache clearing forces synchronous fetches back onto the visitor's render path and erodes the sub-100ms benefit.

Pairing caching with auto-refresh

SWR keeps the server cache fresh; the auto_refresh attribute keeps the browser view live. For a real-time dashboard, combine a fast browser refresh interval with the always-warm server cache:

[tablecrafter source="https://api.example.com/stocks.json" auto_refresh="true" refresh_interval="60000" refresh_last_updated="true"]

Here the page loads instantly from the SWR HTML cache, then the front end polls every 60 seconds for live updates, pausing intelligently while a user is sorting or filtering.

Performance tuning checklist

Next, see data-sources.html for how each source type is fetched and parsed, and shortcode-reference.html for the full list of [tablecrafter] attributes referenced above.