Skip to content

Connectors: preload REST API responses on the Connectors screen - #77353

Open
westonruter wants to merge 13 commits into
trunkfrom
fix/connectors-preload-rest-requests
Open

westonruter wants to merge 13 commits into
trunkfrom
fix/connectors-preload-rest-requests

Conversation

@westonruter

@westonruter westonruter commented Apr 15, 2026 •

Copy link
Copy Markdown
Member

What?

The Connectors screen (options-general.php?page=options-connectors-wp-admin) fires several REST API requests after hydration, delaying the Largest Contentful Paint:

HTTP requests before

Specifically: GET /wp/v2/settings (site entity), OPTIONS /wp/v2/plugins (the canUser( 'create', 'plugin' ) check), a hardcoded GET /wp/v2/plugins/ai/ai used by the AI-plugin callout, and one GET /wp/v2/plugins/<basename> per registered connector — five sequential round-trips with the default connectors.

Why?

These responses are all trivially knowable at render time on the server, so they should be embedded in the initial HTML via createPreloadingMiddleware rather than re-fetched by the client after hydration. This also reduces the number of requests on the server, reducing server load.

Measured impact (median LCP over 10 runs)

Scenario Trunk This PR Δ
Fast 4G 1523 ms 1150 ms −373 ms (≈24.5%)
No throttling 743 ms 518 ms −225 ms (≈30.3%)

How?

Adds a {page-slug}-wp-admin_preload_paths filter to the wp-build page-wp-admin template so any generated admin page can register page-specific preload paths. Hooks it in the Connectors loader to preload the five paths listed in What? above (the existing hardcoded preload in the template only covers the root site fields, not the settings entity the UI actually resolves, so /wp/v2/settings is added too).

After the change, none of the connector-related REST requests fire on page load — they are served from the preload middleware:

HTTP requests after

The two remaining requests come from @wordpress/core-abilities, which Gutenberg enqueues on every admin page — out of scope here, but a candidate for similar preloading.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Opus 4.6
Used for: Investigation, implementation, performance benchmarking, PR description composition

westonruter and others added 2 commits April 14, 2026 20:15
The Connectors screen (options-connectors-wp-admin) previously fired
several REST API requests after hydration — site settings, a plugin
capability check, and one plugin record per registered connector —
delaying first paint.

Add a `{page-slug}-wp-admin_preload_paths` filter to the wp-build
page-wp-admin template so any generated admin page can register
page-specific preload paths. Hook it in the Connectors loader to
preload `/wp/v2/settings`, `OPTIONS /wp/v2/plugins`,
`/wp/v2/plugins/ai/ai?context=edit` (for the AI plugin callout),
and `/wp/v2/plugins/<basename>?context=edit` for each registered
connector with a plugin file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@westonruter westonruter added [Type] Performance Related to performance efforts [Feature] Connectors screen Tracks connectors screen related tasks labels Apr 15, 2026
@github-project-automation github-project-automation Bot moved this to 🔎 Needs Review in WordPress 7.0 Editor Tasks Apr 15, 2026
@github-actions

github-actions Bot commented Apr 15, 2026 •

Copy link
Copy Markdown

Flaky tests detected in 2d6811f.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/27515021732
📝 Reported issues:

@gziolo gziolo left a comment

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.

I haven't tested, but these changes look solid to me.

Comment thread lib/experimental/connectors/load.php Outdated
$preload_paths[] = '/wp/v2/plugins/ai/ai?context=edit';

// getEntityRecord( 'root', 'plugin', <basename> ) per connector in use-connector-plugin.ts.
if ( function_exists( 'wp_get_connectors' ) ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

7.0 (and wp_get_connectors() ) is a pre-requesite for the AI plugin, and assumedly without we also don't care about prefetching wp/v2/settings or OPTIONS wp/v2/plugins.

Would it make sense to just early return at the top of the method instead of partially nesting this logic?

That said, if this is just about PHPStan (and the options-connectors...* filter won't even trigger otherwise ), then I'd just drop the always-true function_exists() and add a rule scoped // @phpstan-ignore <type> (<reason>)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good observation. I opted to remove the function_exists() check entirely in f3269d2 because this options-connectors-wp-admin_preload_paths filter only applies on the connectors admin page which only exists in 7.0 anyway.

Co-authored-by: justlevine <justlevine@git.wordpress.org>

@justlevine justlevine left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm

@jorgefilipecosta jorgefilipecosta left a comment

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.

The change looks good to me, I verified the preloaded endpoint payloads are passed to the client and on the smoke tests I did things look ok.
This PR needs the backport changelog entry and the corresponding core ticket/PR.

@westonruter

Copy link
Copy Markdown
Member Author

@jorgefilipecosta In testing this further, it seems the REST API preloading is not sufficient. In particular, it does not support preloading 404 responses. This means that it still does a REST API request for any plugin that has not been installed. If none of the plugins are installed, then there are always four 404 responses to the REST API:

image

I've addressed this in the core PR: WordPress/wordpress-develop#11790


// AiPluginCallout in routes/connectors-home/ai-plugin-callout.tsx queries this
// hardcoded ID to check whether the WP AI plugin is installed/active.
$preload_paths[] = '/wp/v2/plugins/ai/ai?context=edit';

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

With WordPress/wordpress-develop#11790 this could become as follows to serve the 404 responses to the preload middleware:

$preload_paths[] = array( '/wp/v2/plugins/ai/ai?context=edit', 'GET', array( 200, 404 ) );

However, it would not be compatible with WP 6.9.

// core-data's plugin entity uses the basename with `.php` stripped
// as the record key (see routes/connectors-home/use-connector-plugin.ts).
$basename = preg_replace( '/\.php$/', '', plugin_basename( $connector_data['plugin']['file'] ) );
$preload_paths[] = '/wp/v2/plugins/' . $basename . '?context=edit';

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ditto above, but it would not be compatible with WP 6.9:

$preload_paths[] = array( '/wp/v2/plugins/' . $basename . '?context=edit', 'GET', array( 200, 404 ) );

@westonruter

Copy link
Copy Markdown
Member Author

This PR needs the backport changelog entry and the corresponding core ticket/PR.

See f3269d2 and WordPress/wordpress-develop#11790

westonruter added a commit to westonruter/wordpress-develop that referenced this pull request May 11, 2026
@ellatrix

Copy link
Copy Markdown
Member

This was punted, so moving to next minor.

@ellatrix ellatrix added Backport to WP Minor Release Pull request that needs to be backported to a WordPress minor release and removed Backport to WP 7.0 Beta/RC labels May 19, 2026
@t-hamano t-hamano moved this from 🎯 Needs Core Commit to 🐛 Punted to 7.0.1 in WordPress 7.0 Editor Tasks May 24, 2026
@t-hamano t-hamano moved this from 🐛 Punted to 7.0.1 to 🎯 Needs Core Commit in WordPress 7.0 Editor Tasks May 24, 2026
@cbravobernal cbravobernal moved this from 🎯 Needs Core Commit to 🔎 Needs Review in WordPress 7.0 Editor Tasks Jun 30, 2026
@gziolo

gziolo commented Jul 2, 2026

Copy link
Copy Markdown
Member

@cbravobernal, I'm not the best suited to answer your question as I didn't follow the development. On the higher level, it's a bug fix that improves performance, so it is safe to land in the minor WordPress release.

@cbravobernal cbravobernal moved this from 🔎 Needs Review to 🦵 Punted to 7.1 in WordPress 7.0 Editor Tasks Jul 7, 2026
@jorgefilipecosta

Copy link
Copy Markdown
Member

Gave another pass and left a comment at

function prepareResponse(
responseData: Record< string, any >,
parse: boolean
) {
if ( parse ) {
return Promise.resolve( responseData.body );
}
try {
return Promise.resolve(
new window.Response( JSON.stringify( responseData.body ), {
status: 200,
statusText: 'OK',
headers: responseData.headers,
} )
);
} catch {
// See: https://github.com/WordPress/gutenberg/issues/67358#issuecomment-2621163926.
Object.entries(
responseData.headers as Record< string, string >
).forEach( ( [ key, value ] ) => {
if ( key.toLowerCase() === 'link' ) {
responseData.headers[ key ] = value.replace(
/<([^>]+)>/,
( _, url ) => `<${ encodeURI( url ) }>`
);
}
} );
return Promise.resolve(
parse
? responseData.body
: new window.Response( JSON.stringify( responseData.body ), {
status: 200,
statusText: 'OK',
headers: responseData.headers,
} )
);
.

@westonruter

Copy link
Copy Markdown
Member Author

Gave another pass and left a comment

@jorgefilipecosta I'm not sure I understand.

@jorgefilipecosta

Copy link
Copy Markdown
Member

Gave another pass and left a comment

@jorgefilipecosta I'm not sure I understand.

Hi @westonruter I'm sorry I pasted the wrong link I wanted to link to the comment I left on the core PR WordPress/wordpress-develop#11790 (comment).

@t-hamano

t-hamano commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Based on https://core.trac.wordpress.org/ticket/65215#comment:18, I'd like to punt this to 7.2.

@github-project-automation github-project-automation Bot moved this to 🔎 Needs Review in WordPress 7.1 Editor Tasks Aug 2, 2026
@t-hamano t-hamano moved this from 🔎 Needs Review to 🦵 Punted to 7.2 in WordPress 7.1 Editor Tasks Aug 2, 2026
@t-hamano
t-hamano requested a balanced review from Copilot August 13, 2026 08:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-project-automation github-project-automation Bot moved this to 🔎 Needs Review in WordPress 7.2 Editor Tasks Aug 13, 2026
@t-hamano t-hamano removed the Backport to WP Minor Release Pull request that needs to be backported to a WordPress minor release label Aug 26, 2026
*
* @param string[] $preload_paths Paths to preload.
*/
$preload_paths = apply_filters( '{{PAGE_SLUG}}-wp-admin_preload_paths', $preload_paths );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Naming things: I think it would be better if this was in the form wp_prefix_page_slug.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Updated in 61ba778. The hook name is gutenberg_options_connectors_wp_admin_preload_paths in Gutenberg and wp_options_connectors_wp_admin_preload_paths in Core.

…nectors.php

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the [Package] wp-build /packages/wp-build label Sep 29, 2026
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

🎉 Props

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: westonruter <westonruter@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: peterwilsoncc <peterwilsoncc@git.wordpress.org>
Co-authored-by: ellatrix <ellatrix@git.wordpress.org>
Co-authored-by: cbravobernal <cbravobernal@git.wordpress.org>

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

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric f257d6c trunk % Change
timeToFirstByte 62.4 ms +11.54% -4.49% 61.55 ms +11.37% -4.47% 1.38%
largestContentfulPaint 98 ms +14.29% -2.04% 104 ms +5.77% -7.69% -5.77%
lcpMinusTtfb 36.4 ms +10.03% -3.3% 37.75 ms +15.1% -7.68% -3.58%
wpBeforeTemplate 29.75 ms +18.82% -0.77% 30.18 ms +19.12% -2.95% -1.42%
wpTemplate 26.92 ms +2.53% -3.08% 26.93 ms +1.63% -4.16% -0.04%
wpTotal 58.22 ms +11.2% -4.05% 57.85 ms +10.49% -4.7% 0.64%
wpMemoryUsage 7.63 MB +0% -0% 7.59 MB +0% -0% 0.48%
wpDbQueries 17 +0% -0% 17 +0% -0% 0%

front-end-classic-theme

Metric f257d6c trunk % Change
timeToFirstByte 46.8 ms +3.74% -2.46% 45.85 ms +2.94% -2.84% 2.07%
largestContentfulPaint 100 ms +4% -4% 96 ms +0% -4.17% 4.17%
lcpMinusTtfb 52.1 ms +3.93% -3.55% 48.7 ms +1.64% -1.75% 6.98%
wpBeforeTemplate 27.9 ms +8.03% -1.51% 27.68 ms +5.27% -2.17% 0.79%
wpTemplate 14.93 ms +8.51% -1.21% 14.69 ms +4.15% -3.47% 1.63%
wpTotal 43.75 ms +4% -2.61% 42.94 ms +3% -2.86% 1.89%
wpMemoryUsage 6.11 MB +0% -0% 6.08 MB +0% -0% 0.61%
wpDbQueries 10 +0% -0% 10 +0% -0% 0%

media-processing

Metric f257d6c trunk % Change
mediaProcessingJpeg 404.48 ms +0.49% -0.67% 400.38 ms +0.73% -0.72% 1.02%
mediaProcessingAvif 6021.8 ms +0.19% -0.05% 6075.97 ms +0.18% -0.09% -0.89%
mediaProcessingJpegToAvif 4186.39 ms +0.25% -0.3% 4206.11 ms +0.33% -0.05% -0.47%

media-upload

Metric f257d6c trunk % Change
jpegUploadProcessing 1484.36 ms +32.77% -4.56% 1431.8 ms +0.54% -1.45% 3.67%
pngUploadProcessing 230.16 ms +15.81% -15.83% 187.71 ms +9% -2.42% 22.61%
largeJpegUploadProcessing 1411.7 ms +0.06% -0.1% 1407.94 ms +0.41% -0.34% 0.27%
multipleImageUploadProcessing 1626.09 ms +28.46% -1.31% 1602.04 ms +31.13% -0.89% 1.5%

post-editor

Metric f257d6c trunk % Change
serverResponse 312.55 ms +9.27% -2.76% 333.28 ms +2.09% -10.86% -6.22%
firstPaint 171.79 ms +32.63% -29.86% 178.59 ms +14.77% -13.32% -3.81%
domContentLoaded 868.47 ms +3.76% -2.83% 843.39 ms +1.89% -1.6% 2.97%
loaded 869.65 ms +3.76% -2.83% 844.55 ms +1.89% -1.61% 2.97%
firstContentfulPaint 371.81 ms +2.35% -8.62% 354.6 ms +2.19% -8.68% 4.85%
firstBlock 2598.97 ms +1.48% -2.15% 2476.83 ms +1.75% -0.52% 4.93%
type 17.21 ms +1.8% -1.74% 17.02 ms +6.76% -0.88% 1.12%
typeWithoutInspector 16.96 ms +0.83% -4.66% 16.74 ms +6.69% -4.54% 1.31%
typeWithTopToolbar 21.42 ms +3.31% -5.23% 21.18 ms +7.6% -4.49% 1.13%
typeContainer 7.91 ms +7.08% -6.19% 7.38 ms +3.12% -4.07% 7.18%
focus 65.29 ms +9.08% -3.95% 65 ms +5.83% -4.83% 0.45%
firstFocus 187.44 ms +0% -0% 185.04 ms +0% -0% 1.3%
selectAll 421.45 ms +4.08% -1.11% 433.12 ms +1.46% -3.45% -2.69%
listViewOpen 50.19 ms +7.53% -9.86% 50.47 ms +13.79% -6.06% -0.55%
inserterOpen 20.33 ms +8.56% -1.48% 19.12 ms +18.36% -14.12% 6.33%
inserterHover 2.18 ms +11.93% -8.26% 2.22 ms +4.95% -4.95% -1.8%
inserterSearch 6.91 ms +13.31% -2.89% 7.22 ms +12.05% -7.76% -4.29%
loadPatterns 563.76 ms +3.13% -2.74% 540.59 ms +3.48% -5.16% 4.29%
wpTotal 303.76 ms +9.49% -2.84% 324.58 ms +2.03% -11.2% -6.41%
wpMemoryUsage 13.18 MB +0% -0% 13.14 MB +0% -0% 0.29%
wpDbQueries 54 +0% -0% 54 +0% -0% 0%

site-editor

Metric f257d6c trunk % Change
serverResponse 316.73 ms +0.6% -11.68% 277.38 ms +11.37% -1.1% 14.19%
firstPaint 199.08 ms +78.13% -12.62% 204.66 ms +10.59% -9.07% -2.73%
domContentLoaded 876.17 ms +1.99% -2.99% 886.56 ms +0.72% -0.95% -1.17%
loaded 877.26 ms +2.02% -2.98% 887.62 ms +0.72% -0.95% -1.17%
firstContentfulPaint 354.71 ms +4.62% -0.75% 357.77 ms +1.07% -2.01% -0.86%
firstBlock 3107.46 ms +1.91% -0.3% 3094.06 ms +0.16% -0.53% 0.43%
type 15.54 ms +1.09% -5.47% 15.08 ms +4.18% -2.45% 3.05%
navigate 90.66 ms +0.9% -4.43% 98.42 ms +20.19% -11.04% -7.88%
loadPatterns 1009.33 ms +14.52% -2.14% 980.87 ms +1.44% -0.6% 2.9%
loadPages 1031.23 ms +0.96% -0.61% 1023.87 ms +2.91% -0.79% 0.72%
wpTotal 308.4 ms +0.6% -12.03% 268.94 ms +10.88% -1.14% 14.67%
wpMemoryUsage 12.14 MB +0% -0% 12.10 MB +0% -0% 0.32%
wpDbQueries 44 +0% -2.27% 44 +0% -2.27% 0%

e61c5b1 Run

@jorgefilipecosta jorgefilipecosta left a comment

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.

I did another pass and this seems ready. Thank you for the iterations @t-hamano!

Comment thread lib/compat/plugin/connectors.php Outdated
Comment on lines +45 to +46
* @param string[] $preload_paths Paths already queued for preloading.
* @return string[] Paths with the Connectors-specific requests appended.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Just noticing this is incorrect.

The block_editor_preload_paths filter is documented as this being (string|string[])[].

This is accurate, with the additional PHPStan array shape being optional (and I'm not entirely sure if the supported methods are correct):

Suggested change
* @param string[] $preload_paths Paths already queued for preloading.
* @return string[] Paths with the Connectors-specific requests appended.
* @param array<string|string[]> $preload_paths Paths already queued for preloading.
* @return array<string|string[]> Paths with the Connectors-specific requests appended.
*
* @phpstan-param array<non-falsy-string|array{ non-falsy-string, 'GET'|'OPTIONS' }> $preload_paths
* @phpstan-return array<non-falsy-string|array{ non-falsy-string, 'GET'|'OPTIONS' }>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in c6ef13f

* them here so the responses are embedded in the initial HTML rather
* than fetched over the network after hydration.
*
* @param string[] $preload_paths Paths to preload.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Suggested change
* @param string[] $preload_paths Paths to preload.
* @param array<string|string[]> $preload_paths Paths to preload.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in e61c5b1

This branch has not been deployed

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

Labels

[Feature] Connectors screen Tracks connectors screen related tasks [Package] wp-build /packages/wp-build [Type] Performance Related to performance efforts

Projects

Status: 🔎 Needs Review

Development

Successfully merging this pull request may close these issues.

9 participants