Skip to content

Bug: client-side navigation drops incoming content that follows a hydrated clientEntry #11646

Description

@luisrudge

When Remix applies an incoming document during a client-side navigation, any content positioned after a hydrated clientEntry(...) is silently discarded. The URL updates, the elements before that position render, and the rest never appears. No console warning, no error.

Repro: https://github.com/luisrudge/remix-nav-repro (remix@3.0.0-beta.5)

pnpm install && pnpm exec playwright install chromium
pnpm test
PASS control: #tail exists after /control -> /plain
PASS control: #tail exists after /control -> /divroot
FAIL island: expected #tail after /island -> /plain; found body children header, script
FAIL nested: expected #tail after /nested -> /divroot; found body children div, script, root children header
FAIL Back:   expected #tail after /plain -> /island -> Back; found body children header, script

/island renders a trivial hydrated entry as the body's first child. Navigating to /plain, whose body is [header, div#tail], produces a page containing only the <header>.

Isolation — the island is identical in every row; only its position and wrapper tag change:

From Entry sits at Destination root Result
/island/plain body index 0 <header> #tail dropped
/plain/island → Back body index 0 <header> #tail dropped
/nested/divroot inside <div> <div> #tail dropped one level deeper
/control/plain inside <main> <header> ok
/control/divroot inside <main> <div> ok

It's not about hydration. The control island hydrates too — the test clicks its counter before every navigation — and it works fine. What matters is where the entry sits.

Wrapping the entry doesn't fix it either. /nested and /control are the same page with a different wrapper tag, going to the same place. /nested uses a <div>, the destination also starts with a <div>, so the diff goes inside both and drops content one level down instead. /control uses a <main>, nothing matches, and it works.

So the only thing that saves you is picking a wrapper tag no other page uses at its root. That's easy to get wrong, and it breaks again the moment another page starts using that tag.

I hit this in a real app. It looked like a blank page after clicking a link or hitting Back, and took a while to track down because nothing errors.

Environment: remix@3.0.0-beta.5, Node 24.18.0, macOS 15, Chromium via Playwright 1.62.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions