Skip to content

Block Supports: Prevent Additional CSS duplication inside Query Loop#78282

Open
Mustafabharmal wants to merge 3 commits into
WordPress:trunkfrom
Mustafabharmal:fix/78278-deduplicate-custom-css-in-query-loop
Open

Block Supports: Prevent Additional CSS duplication inside Query Loop#78282
Mustafabharmal wants to merge 3 commits into
WordPress:trunkfrom
Mustafabharmal:fix/78278-deduplicate-custom-css-in-query-loop

Conversation

@Mustafabharmal
Copy link
Copy Markdown
Contributor

What?

Additional CSS added to a block inside a Query Loop was being inserted once per post in the loop, causing identical CSS rules to be duplicated N times in the output.

Closes #78278

Why?

gutenberg_render_custom_css_support_styles is hooked to render_block_data, which fires on every block render. Inside a Query Loop the same block template is rendered once per post, so wp_add_inline_style was called N times with the same CSS content.

Since wp_unique_id_from_values deterministically generates the same hash for the same block data, each loop iteration produced the same $class_name and the same $processed_css.

How?

Added a static $enqueued_class_names array inside the function. Before calling wp_add_inline_style, we check whether CSS for this class name has already been enqueued. If it has, the call is skipped. The block's class name attribute is still set on every render (needed for correct markup), only the redundant style injection is prevented.

Testing Instructions

  1. Open the Site Editor.
  2. Add a Query Loop block.
  3. Select a block inside the loop template (e.g. Post Title) and add custom CSS via the Additional CSS panel.
  4. Save, publish a few posts, and view the front end.
  5. Inspect <style id="wp-block-custom-css-inline-css">, the CSS rules should appear exactly once regardless of how many posts are in the loop.

Screenshots or screencast

Before:

Test.Before.Fix.mov

After:

Test.After.Fix.mov

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: Mustafabharmal <mustafabharmal@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: westonruter <westonruter@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: benniledl <benniledl@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@t-hamano t-hamano added [Feature] Block API API that allows to express the block paradigm. [Type] Performance Related to performance efforts labels May 16, 2026
@github-project-automation github-project-automation Bot moved this to 🔎 Needs Review in WordPress 7.0 Editor Tasks May 16, 2026
Copy link
Copy Markdown
Contributor

@t-hamano t-hamano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, This PR might be considered for shipping in the 7.0 release as a performance fix.

My question is why special deduplication is only needed for custom CSS. The style engine should already have deduplication functionality. Perhaps there's a reason why custom CSS support couldn't be integrated into the style engine 🤔

@t-hamano t-hamano requested review from glendaviesnz and ramonjd May 16, 2026 04:22
@ramonjd
Copy link
Copy Markdown
Member

ramonjd commented May 16, 2026

perhaps there's a reason why custom CSS support couldn't be integrated into the style engine

@glendaviesnz will know better than I about the "why".

I can say that the style engine already dedupes for rules inside a rendered stylesheet, but it's fed structured data (property/value pairs in declaration collections).

My guess is that, because the custom CSS field accepts freeform text, it would have had to undergo a structural validation and sanitization step.

There are a suite of scenarios where a standards-based CSS Processor could be the glue for all this. Just pulling a few examples:

I think @sirreal and friends might be looking at this sometime in the next couple of cycles. Don't quote me on it.

I imagine, one day, the style engine could use this tool as a layer between ingestion and deduplication.

@ramonjd
Copy link
Copy Markdown
Member

ramonjd commented May 16, 2026

This appears to be the right fix for now. It's contained and can be abstracted later.

Some regression tests would be good, but I'll leave it you whether you do them here or in the Core backport patch 👍🏻

@t-hamano
Copy link
Copy Markdown
Contributor

@Mustafabharmal, have you ever created a core backport patch? Could you try this out, referring to this document?

Comment on lines +58 to +73
* Track which class names have already had their CSS enqueued to prevent
* duplicate styles when the same block is rendered multiple times inside
* a Query Loop (render_block_data fires once per loop iteration).
*/
wp_register_style( 'wp-block-custom-css', false, array( 'global-styles' ) );
wp_add_inline_style( 'wp-block-custom-css', $processed_css );
static $enqueued_class_names = array();

if ( ! isset( $enqueued_class_names[ $class_name ] ) ) {
$enqueued_class_names[ $class_name ] = true;
/*
* Register and add inline style for block custom CSS.
* The style depends on global-styles to ensure custom CSS loads after
* and can override global styles.
*/
wp_register_style( 'wp-block-custom-css', false, array( 'global-styles' ) );
wp_add_inline_style( 'wp-block-custom-css', $processed_css );
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-posting my wordpress-develop comment: WordPress/wordpress-develop#11859 (comment)

A static variable can be very problematic for unit tests. I don't recommend using it. I suggest instead that we look at what wp_add_inline_style() is doing under the hood. It calls WP_Styles::add_inline_style( $handle, $code ) which in turn adds the provided code to an array of existing inline styles and then passes that amended array to WP_Styles::add_data( $handle, 'after', $after ).

So I think a better approach would be to use the underlying WP_Styles::$extra array instead of a static $enqueued_class_names here:

Suggested change
* Track which class names have already had their CSS enqueued to prevent
* duplicate styles when the same block is rendered multiple times inside
* a Query Loop (render_block_data fires once per loop iteration).
*/
wp_register_style( 'wp-block-custom-css', false, array( 'global-styles' ) );
wp_add_inline_style( 'wp-block-custom-css', $processed_css );
static $enqueued_class_names = array();
if ( ! isset( $enqueued_class_names[ $class_name ] ) ) {
$enqueued_class_names[ $class_name ] = true;
/*
* Register and add inline style for block custom CSS.
* The style depends on global-styles to ensure custom CSS loads after
* and can override global styles.
*/
wp_register_style( 'wp-block-custom-css', false, array( 'global-styles' ) );
wp_add_inline_style( 'wp-block-custom-css', $processed_css );
}
* Track which class names have already had their CSS enqueued to prevent
* duplicate styles when the same block is rendered multiple times inside
* a Query Loop (render_block_data fires once per loop iteration).
*/
$handle = 'wp-block-custom-css';
if ( ! wp_style_is( $handle, 'registered' ) ) {
wp_register_style( $handle, false, array( 'global-styles' ) );
}
$after_styles = wp_styles()->get_data( $handle, 'after' );
if ( ! is_array( $after_styles ) ) {
$after_styles = array();
}
if ( ! in_array( $processed_css, $after_styles, true ) ) {
wp_add_inline_style( $handle, $processed_css );
}

@t-hamano
Copy link
Copy Markdown
Contributor

t-hamano commented Jun 3, 2026

@Mustafabharmal, do you have the bandwidth to address the feedback added to this PR and the core PR? Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backport to WP Minor Release Pull request that needs to be backported to a WordPress minor release [Feature] Block API API that allows to express the block paradigm. [Type] Performance Related to performance efforts

Projects

Status: 🔎 Needs Review

Development

Successfully merging this pull request may close these issues.

Additional CSS inside Query Loop is inserted multiple times

4 participants