Clever Linked Terms 1.2.1 — 3.6 MB of inline JSON per page on a 502-term glossary (with patches)
Hi Jeff,
I run a French glossary of 502 terms on youtips.com using Clever Linked Terms 1.2.1, and I had to disable the WordPress Content module because every page on the site had grown to roughly 4 MB. I dug into the cause and wanted to share what I found, since I think three of these are straightforward fixes on your side and would benefit anyone running a large glossary.
1. The whole glossary is serialised inline on every page
Clevlite_WPContent::enqueue_assets() builds the payload with 'tip' => $t['definition'], where the definition is the entire post_content of the term, stripped of tags. With 502 terms averaging 11,110 characters each (longest: 48,866), the window.CLEVLITE = {...} inline script measures 3.66 MB, and it is re-sent on every single page load.
Measured on my install:
| Full inline payload | 3.66 MB |
— of which tip values |
3.15 MB |
| Same payload without tips | 519 KB |
| Same payload, tips capped at 200 chars | 617 KB |
The tooltip only ever displays the first couple of lines, so the remaining ~3 MB is downloaded and parsed for nothing, on every page.
2. The tooltip-length setting is read but never applied
This one looks like a simple oversight. In includes/class-portal-assets.php, build_terms_payload() opens with:
private static function build_terms_payload(): array {
$tip_chars = Clevlite_Settings::tipchars(); // line 169
$tip_chars is never used anywhere below. The Tooltip characters setting in the admin UI therefore has no effect at all. Applying it in both payload builders would fix the size problem on its own.
3. The two payload builders disagree
Clevlite_PortalAssets::build_terms_payload() (FluentCommunity) prefers post_excerpt when it exists:
$tip = $t->post_excerpt ?: wp_strip_all_tags($t->post_content);
Clevlite_WPContent::get_all_terms() (WordPress content) ignores post_excerpt entirely and always takes the full post_content. Aligning the WordPress path on the FluentCommunity behaviour, and honouring tipchars in both, would make the two modules consistent.
4. Definitions are re-encoded in base64 on every occurrence
In assets/linked-terms.js, lines 621 and 623, each matched term gets the full definition base64-encoded into a data-clevlite-tip-b64 attribute:
'... data-clevlite-tip-b64="' + escAttr(b64encode(termData.tip)) + '"'
Base64 adds ~33%, and the attribute is written once per occurrence. With maxLinks = 4, an 11 KB definition appearing four times adds roughly 60 KB to the DOM for that single term. Looking the definition up by data-term-id in the existing array at hover time would avoid the duplication entirely.
5. the_content is re-entered on itself
includes/class-cpt.php, lines 746–748:
remove_filter('the_content', [$this, 'append_relationships_to_single_term'], 20);
$rendered = apply_filters('the_content', $raw_content);
add_filter('the_content', [$this, 'append_relationships_to_single_term'], 20);
Only your own callback is detached, so every third-party the_content filter runs twice on glossary term pages. In my case a related-terms block I had added rendered twice, with a duplicated HTML id. I worked around it with a nesting-depth guard, but other plugins hooking the_content will hit the same thing without knowing why. Passing the raw content through a narrower set of filters, or setting a public flag while re-entering, would spare them the surprise.
6. Feature request: allow the glossary CPT in Post Types
includes/settings-tab-wordpress.php, line 29:
unset($clevlite_post_types['clevlite_term'], $clevlite_post_types['attachment']);
I understand excluding attachment, but excluding clevlite_term means glossary entries can never link to one another. On a 502-term glossary, that is exactly where interlinking carries the most value — and it leaves every entry as a dead end for crawlers. If the concern is self-linking, filtering out the current post ID from the payload on singular views would handle it. An opt-in checkbox would be very welcome.
What I did on my side
Rather than patch your plugin (which updates would overwrite), I wrote two small mu-plugins:
- One intercepts the inline script before it is printed, truncates each
tipto 300 characters, and serves the payload from a content-hashed external.jsfile. Pages went from ~3.8 MB to 170 KB; the map is now downloaded once per visit instead of once per page, and cached for a year. - One adds a server-side "See also" block at the bottom of each glossary entry, since the plugin cannot link entries to each other.
Both are workarounds for the outside. Fixes 2 and 3 in particular seem cheap and would help every install with a sizeable glossary.
Happy to test a patch or share the mu-plugin code if that is useful. Thanks for the plugin — the tooltip UX itself is genuinely nice, and the glossary is central to my site.
Best regards,
Fabien Fons
YouTips — youtips.com
Fons thank you for reporting, I will have an update pushed out to WP shortly.