BRFPANEL localised twice — identical ~107 KB payload duplicated in frontend HTML (3.1.8.7)

Hello,

I was trying to optimize my site and I’m reporting to you with the help of AI the problem I discovered :

BRFPANEL localised twice — identical ~107 KB payload duplicated in frontend HTML

Bricksforge 3.1.8.7 · includes/Frontend.php:596-597

Summary

On the frontend, Bricksforge emits its entire BRFPANEL payload twice. The same
$args array is localised to two different handles that both load on the frontend:

// includes/Frontend.php:596-597
wp_localize_script('bricksforge-panel', 'BRFPANEL', $args);
wp_localize_script('bricksforge-node-editor', 'BRFPANEL', $args);

On our homepage that is 107,421 characters of JSON printed twice. With
Perfmatters’ “defer inline JavaScript” converting inline scripts to base64 data
URIs, each copy becomes ~143 KB of HTML, so the duplicate costs ~143 KB of raw
HTML per page view
.

Because bricksforge-node-editor already declares bricksforge-panel as a
dependency (includes/Assets.php:201-205), the BRFPANEL global is guaranteed to
be defined before bricksforge_nodes_editor.js executes. The second localisation
therefore appears redundant.

Environment

Bricksforge 3.1.8.7
Bricks 2.3.10 (child theme active)
WordPress 7.0.2
PHP 8.5.6
Server LiteSpeed (OpenLiteSpeed 1.8.4)
Active Bricksforge tools Element Conditions, Bricksforge Panel, Scroll Smoother, Email Designer, API Query Builder (ids 2, 6, 7, 13, 18)
Other relevant plugins Perfmatters 2.6.6, LiteSpeed Cache 7.8.1, BricksExtras 1.7.2, Advanced Themer 3.3.15
includes/Frontend.php sha1 b21fec051c4cf4215b667338c8f0d8525a9dca65

Note: “Animation Controls” is not among our activated tools — the payload is
still emitted twice because the timelines path runs regardless.

Evidence

Both blocks were extracted from the live page and decoded. They are byte-for-byte
identical except the trailing sourceURL comment WordPress appends
:

blob A: 107,478 chars → ends "...}};\n//# sourceURL=bricksforge-panel-js-extra"
blob B: 107,484 chars → ends "...}};\n//# sourceURL=bricksforge-node-editor-js-extra"

first divergence at character 107,464 — i.e. only inside that comment

Payload composition (identical in both):

Key Bytes
panelNodes 90,269
panel 16,373
everything else ~265
total 106,907

Impact measured on our site

Homepage, before and after suppressing the second copy:

Before After Saved
Raw HTML 1,528,011 B 1,384,604 B −143,407 B
gzip (as served) 295,882 B 256,529 B −39,353 B (−13%)
brotli 182,006 B 181,823 B −183 B

The gzip figure is the meaningful one for anyone not serving brotli: gzip’s 32 KB
window cannot reach back far enough to dedupe a 143 KB repeat, so the duplicate
costs real transfer bytes. Brotli’s larger window collapses it almost entirely,
which is presumably why this has gone unnoticed on brotli-enabled hosts.

Workaround we are using

A wp_enqueue_scripts hook at PHP_INT_MAX that drops the node-editor copy, but
only when it is provably safe:

add_action('wp_enqueue_scripts', function () {
    if (is_admin()) return;
    if (function_exists('bricks_is_builder') && bricks_is_builder()) return;

    global $wp_scripts;
    if (!($wp_scripts instanceof WP_Scripts)) return;

    $node  = $wp_scripts->registered['bricksforge-node-editor'] ?? null;
    $panel = $wp_scripts->registered['bricksforge-panel'] ?? null;
    if (!$node || !$panel) return;

    // both must carry the same global, and the dependency must guarantee ordering
    if (strpos($node->extra['data']  ?? '', 'var BRFPANEL') === false) return;
    if (strpos($panel->extra['data'] ?? '', 'var BRFPANEL') === false) return;
    if (!in_array('bricksforge-panel', (array) $node->deps, true)) return;

    unset($wp_scripts->registered['bricksforge-node-editor']->extra['data']);
}, PHP_INT_MAX);

Verification that nothing broke

Compared against an unmodified control site, with the workaround active:

  • window.BRFPANEL still exposes all 7 keys (ajaxNonce, ajaxurl, postId,
    panel, panelNodes, panelActivated, activeTemplate)
  • BRFPANEL.panelNodes still holds its 14 entries; BRFPANEL.panel still length 1
  • GSAP reports 92 tweens on both — identical
  • ScrollTrigger / SplitText present on both; zero console errors
  • bricksforge_panel.js and bricksforge_nodes_editor.js both still load
  • Our header GSAP menu animation behaves identically

Suggested fix

If bricksforge_nodes_editor.js genuinely only reads the global (rather than
needing its own -js-extra block), the second call can simply be dropped — the
dependency already guarantees ordering.

If a belt-and-braces guarantee is preferred, an alternative is to localise onto the
lowest handle in the dependency chain only, or to register a tiny shared
bricksforge-data handle that both scripts depend on and localise once onto that.

Either way the frontend payload halves.

Aside — a related opportunity

panelNodes alone is 90 KB of the 107 KB, and is emitted in full on every frontend
page. If timelines/nodes can be filtered to those actually referenced by the
rendered page (the timelines path already supports a per-post “load on” restriction),
that would be a further substantial reduction. Entirely separate from the
duplication bug, but in the same area of the code.


Thank you for reading. Please let me know if you can improve this based on what the AI suggests :folded_hands:t3:

Kind regards,
Barnabas

Hi Barnabas,

thanks for this — confirmed on our side and added to the bug tracker.

Your workaround is safe in the meantime. The three guards you added check exactly the conditions that matter, and because node-editor is always enqueued together with panel, the ordering guarantee holds even after dropping the second data block.

Thanks again!

Best,
Daniele

Hi Daniele,

Thank you for noting it! I appreciate it.

Kind regards,
Barnabas