Skip to content

[6.x] Only preload meta for the selected link type - #15113

Merged
jasonvarga merged 2 commits into
6.xfrom
14664
Aug 5, 2026
Merged

[6.x] Only preload meta for the selected link type#15113
jasonvarga merged 2 commits into
6.xfrom
14664

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request fixes an issue where entry edit pages containing lots of Link fields inside nested Replicators would take a long time to load, and often end in a blank page or a timeout.

This was happening because Link::preload() built the full nested relationship meta for every registered link type, on every instance of the field, whether or not that type was actually selected. That meta is derived entirely from the field's config, so a page with hundreds of link fields shipped hundreds of byte-identical copies of it. In the reported case, 1,008 link fields produced 1,008 identical copies of the entries meta, accounting for 97% of an 11mb data-page payload.

This PR fixes it by only preloading meta for the type matching the field's initial option. The rest send null, and the Control Panel fetches them from the existing fields/field-meta endpoint the first time the user picks that type. On a test entry with 480 cells (960 link fields), the payload drops from 9.5mb to 1.9mb.

Fixes #14664

`Link::preload()` built the full nested relationship meta for every registered
link type on every instance of the field. In a nested Replicator with hundreds
of link fields, that's hundreds of byte-identical copies of the same config
derived payload. The rest are now fetched from `fields/field-meta` the first
time the user picks that type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvarga jasonvarga 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.

Requires changes — found a regression while reviewing this PR, reproduced live in a sandbox.

Issue

LinkFieldtype.vue now gates rendering the nested fieldtype component on matchedType.meta being truthy:

<component v-else-if="matchedType && matchedType.meta" ... />
<Icon v-else-if="matchedType" name="loading" class="size-4 self-center" />

And Link::preload() (src/Fieldtypes/Link.php) only preloads meta for the initial option now; every other type gets meta: null.

The problem: null is also exactly what a legitimately-loaded meta looks like for any fieldtype that doesn't override Fieldtype::preload() — the base implementation just returns null (src/Fields/Fieldtype.php:374-377). So any custom Link::extend() type built on a plain fieldtype (e.g. text, toggle, integer) will have meta === null forever — both from the initial preload and from every subsequent fields/field-meta fetch, since that endpoint just calls preload() again and gets the same null back.

Since the component only renders once meta is truthy, the field gets stuck on the loading spinner permanently and becomes completely unusable, with no error shown to the user. This isn't hypothetical — it's exactly the shape of this repo's own TestBasicLinkType fixture in tests/Fieldtypes/LinkTest.php (fieldtype() => ['type' => 'text']). I reproduced it live: registered a custom email Link type backed by the text fieldtype, added it to a blueprint, selected it in the CP — permanent spinner, field never renders.

Suggested fix

Track fetch status explicitly instead of inferring it from the value — add a metaLoaded boolean alongside meta:

src/Fieldtypes/Link.php:

$types[$handle] = [
    ...
    'meta' => $handle === $initialOption ? $nestedFieldtype->preload() : null,
    'metaLoaded' => $handle === $initialOption,
    'selected' => $selected ? [$selected] : [],
];

LinkFieldtype.vue:

<component v-else-if="matchedType && matchedType.metaLoaded" ... />
<Icon v-else-if="matchedType" name="loading" ... />
loadTypeMeta(handle) {
    const type = this.meta.types[handle];
    if (!type || type.metaLoaded) return Promise.resolve();
    ...
},

updateTypeMeta(handle, typeMeta) {
    this.updateMeta({
        ...this.meta,
        types: { ...this.meta.types, [handle]: { ...this.meta.types[handle], meta: typeMeta, metaLoaded: true } },
    });
},

Everything else here looks solid — the built-in Entry/Asset path (the actual reported performance issue) is correctly fixed and well tested. This is scoped to custom Link types built on non-preloadable fieldtypes.

(Comment posted via Claude)

`null` is also what a legitimately loaded meta looks like for any fieldtype
that doesn't override `Fieldtype::preload()`, so custom link types built on
plain fieldtypes (like `text`) were stuck on the loading spinner forever.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvarga jasonvarga 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.

Confirmed fixed in 168b130 — the metaLoaded flag correctly separates "not fetched yet" from "legitimately null," so custom Link types on non-preloadable fieldtypes (e.g. text) no longer get stuck loading. Verified:

  • Read the diff line-by-line, matches the suggested fix.
  • it_marks_meta_as_loaded_for_a_type_whose_fieldtype_preloads_nothing (PHP) and does not request meta again for a type whose fieldtype has no meta to preload (JS) directly cover the scenario I reproduced.
  • ./vendor/bin/phpunit tests/Fieldtypes/LinkTest.php passes locally (35/35).
  • CI green.

Approving.

(Comment posted via Claude)

@jasonvarga
jasonvarga merged commit 433aafc into 6.x Aug 5, 2026
22 checks passed
@jasonvarga
jasonvarga deleted the 14664 branch August 5, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large CP entry edit payload with nested Replicator + Link fields

2 participants