Skip to content

feat(astro): add observeDynamicLinks option for prefetch settings#16583

Open
rururux wants to merge 1 commit into
withastro:mainfrom
rururux:prefetch-mutation-observer
Open

feat(astro): add observeDynamicLinks option for prefetch settings#16583
rururux wants to merge 1 commit into
withastro:mainfrom
rururux:prefetch-mutation-observer

Conversation

@rururux

@rururux rururux commented May 4, 2026

Copy link
Copy Markdown
Member

fixes #13297

Changes

In the current implementation, enabling prefetch settings causes links to be collected via document.getElementsByTagName('a') on the initial page load.
While this approach works for most cases, it fails to cover certain scenarios, such as the one reported in issue #13297.

The root cause of issue #13297 is that when a component with the server:defer attribute uses top-level await, its links are rendered after the Promise resolves, meaning they are not captured by the initial document.getElementsByTagName('a') call.
More broadly, the same issue occurs whenever links are dynamically added to the DOM after the initial page load.

To address this, this pull request adds the observeDynamicLinks option, which uses a MutationObserver to watch for dynamically added link elements.
This ensures that links added after the initial render are properly captured.

This feature is opt-in.

Why MutationObserver?

Investigation process
  • First, I checked the code around server:defer to see if it used any event notifications.
  • It did not, but I found that the prefetch code [uses the astro:page-load event].
    document.addEventListener('astro:page-load', () => {
  • When I looked into whether astro:page-load could be used here, I found that the constant defining this event name is marked as @deprecated, so I decided to look for a different approach.
    /** @deprecated This will be removed in Astro 7 */
    export const TRANSITION_PAGE_LOAD = 'astro:page-load';
  • I also considered that the root cause of issue Prefetch not working in components with server:defer and top-level await #13297 is not specific to server:defer, the same problem can occur in other situations where links are added to the DOM dynamically. For this reason, I wanted an approach that resolves the issue within the prefetch code itself, rather than adding prefetch-dependent logic to the server:defer side.
  • As a result, I concluded that MutationObserver is the best approach, as it is well-suited for observing DOM changes and allows the fix to be fully self-contained within the prefetch implementation.

Testing

Added tests to verify that the observeDynamicLinks option works correctly in the following scenarios:

Docs

/cc @withastro/maintainers-docs for feedback!

@changeset-bot

changeset-bot Bot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 717a9d9

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added pkg: astro Related to the core `astro` package (scope) docs pr semver: minor Change triggers a `minor` release labels May 4, 2026

@github-actions github-actions Bot 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.

This PR is blocked because it contains a minor changeset. A reviewer will merge this at the next release if approved.


// https://astro.build/config
export default defineConfig({
adapter: node({ mode: 'standalone' }),

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.

This adapter is required to run tests using components that utilize the server:defer property.

@codspeed-hq

codspeed-hq Bot commented May 4, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 18 untouched benchmarks


Comparing rururux:prefetch-mutation-observer (717a9d9) with main (17f1867)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (7711e47) during the generation of this report, so 17f1867 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@delucis

delucis commented Jun 12, 2026

Copy link
Copy Markdown
Member

This feature is opt-in.

Is there a reason this is opt-in and not a default? Are the drawbacks to this that means we’d want to avoid it most of the time?

@rururux

rururux commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

Honestly, the main reason was that I expected most cases wouldn't require MutationObserver-based detection, so I went with opt-in first to limit the impact rather than changing the default behavior right away.
That said, I can't think of any real drawbacks, and I did have in mind that making MutationObserver the default would be the right direction eventually if there are no issues with it.

Happy to change it to default behavior if that's what you'd prefer.

@ematipico

Copy link
Copy Markdown
Member

We don't know what's the impact on real-world applications, so I think it's best to have it opt-in for now

@delucis

delucis commented Jun 24, 2026

Copy link
Copy Markdown
Member

It’s true that a global MutationObserver like that does feel a bit scary — just because it needs to watch all DOM mutations and run our code. That could certainly be an issue performance-wise on large or dynamic sites.

I wonder if another option could be to expose rescanLinks method from the prefetch module? We could then document to users that for dynamically added links they should use that. Chances are they may have a better idea anyway of when this is needed.

And for this specific issue (server:defer) I feel like the script managing server islands could probably do this itself without needing an observer? It already knows when its updated the DOM, so could update prefetch state when that happens.

These are both areas I don’t know super well, so don’t take this as a “We should do this”, but just sharing some thoughts to see if they’d help.

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

Labels

docs pr pkg: astro Related to the core `astro` package (scope) semver: minor Change triggers a `minor` release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prefetch not working in components with server:defer and top-level await

3 participants