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.
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:
- Data cache — the raw, parsed payload fetched from your source (JSON, CSV, or Google Sheet), keyed with the
tc_cache_prefix. - HTML cache — the fully rendered, crawlable table markup plus an embedded data snapshot, keyed with the
tc_html_prefix.
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).
- 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.
- Fresh cache hit (< 5 min old): The stored HTML is returned instantly. No network request, no revalidation.
- 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:
- Expiring transients (the SWR caches), which WordPress garbage-collects automatically.
- A single
tc_tracked_urlsoption holding up to the 50 most-recent source URLs, used for cache warming. - A handful of plugin settings options.
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:
| Mechanism | Trigger | Behavior |
|---|---|---|
| Hourly warmer | WP-Cron event tc_refresher_cron | Re-fetches every tracked source URL and refreshes its data transient. |
| On-demand revalidation | Single event tc_refresh_single_source | Scheduled 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:
| Setting | Value | Purpose |
|---|---|---|
| Virtual scroll threshold | 500 rows | Above this size, virtual scrolling activates automatically. |
| Rows rendered | 50 | Number of visible rows kept in the viewport. |
| Buffer rows | 10 | Extra 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
- Install a persistent object cache (Redis or Memcached) so transients live in memory rather than the database.
- Use
includeto trim heavy APIs down to the columns you actually display — smaller payloads cache faster and hydrate quicker. - Let virtual scrolling handle large datasets rather than setting an enormous
per_page. - Run
wp tablecrafter warm-cachefrom a real system cron on low-traffic sites. - Leave the default 1-hour TTL in place unless your data genuinely changes by the minute — combine SWR with
auto_refreshinstead.
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.