Skip to content

fix: include slot scripts in server island response#15633

Merged
matthewp merged 9 commits intowithastro:mainfrom
jwoyo:fix/server-island-deduplicate-scripts
Feb 24, 2026
Merged

fix: include slot scripts in server island response#15633
matthewp merged 9 commits intowithastro:mainfrom
jwoyo:fix/server-island-deduplicate-scripts

Conversation

@jwoyo
Copy link
Contributor

@jwoyo jwoyo commented Feb 24, 2026

Fixes: #15622 Components passed as slots to server:defer components had their <script> tags silently dropped. renderSlotToString separates HTML from render instructions, and only the HTML survived serialization via content.toString().

This appends script instructions to the slot HTML before encryption, so the island response includes the scripts needed for interactivity.

Changes

  • Append type === 'script' render instructions to slot HTML in ServerIslandComponent.getIslandContent() before the payload is encrypted

Testing

Minimal reproduction: https://github.com/jwoyo/astro-server-island-bug

  1. Component with <script> passed as slot to server:defer wrapper
  2. Before: island response contains HTML but no <script> → no interactivity
  3. After: island response includes the script → works

Docs

No docs needed — this is a bug fix restoring expected behavior.

One could argue that may result in duplicate script injection when the same component is also used statically on the page. But deduplication is not feasible at this point in the render pipeline due to concurrent buffering - the need to build idempotent scripts (that don't hurt when executed twice) could be part of the docs.

Components passed as slots to server:defer components had their scripts
silently dropped because renderSlotToString separates HTML content from
render instructions, and only the HTML was serialized into the island
payload via content.toString().

This appends script instructions to the slot HTML before encryption,
so the island response includes the scripts needed for interactivity.

This may result in duplicate script injection when the same component
is also used statically on the page. Deduplication is not feasible here
due to the concurrent BufferedRenderer model — renderedScripts is not
yet populated at the time island payloads are constructed.
@changeset-bot
Copy link

changeset-bot bot commented Feb 24, 2026

🦋 Changeset detected

Latest commit: 3a5d9a3

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 the pkg: astro Related to the core `astro` package (scope) label Feb 24, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 24, 2026

Merging this PR will not alter performance

✅ 18 untouched benchmarks


Comparing jwoyo:fix/server-island-deduplicate-scripts (3a5d9a3) with main (753964f)1

Open in CodSpeed

Footnotes

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

@jwoyo
Copy link
Contributor Author

jwoyo commented Feb 24, 2026

Failing check seems to be unrelated and flaky.

Copy link
Member

@florian-lefebvre florian-lefebvre left a comment

Choose a reason for hiding this comment

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

Can you add a test for it? We probably have a file called server-islands.test.js and an associated fixture

@jwoyo
Copy link
Contributor Author

jwoyo commented Feb 24, 2026

Sure thing @florian-lefebvre ! I have added the example from my minimal repro. I verified that the test fails without the fix.

Copy link
Member

@florian-lefebvre florian-lefebvre left a comment

Choose a reason for hiding this comment

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

Code LGTM

// to server:defer components retain their scripts in the island response.
// renderSlotToString returns a SlotString (typed as string) that carries
// render instructions stripped from the HTML content.
const slotContent = content as unknown as import('./slot.js').SlotString;
Copy link
Contributor

Choose a reason for hiding this comment

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

can you use ar regular top-level type import here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, that should work. This was an oversight and there no particular reason for this kind of import.

@matthewp matthewp merged commit 9d293c2 into withastro:main Feb 24, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: astro Related to the core `astro` package (scope)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

<script> tags silently dropped from components passed as slots to server:defer components

3 participants