Skip to content

feat: add yew-link crate for unified SSR/CSR data fetching#4027

Open
Madoshakalaka wants to merge 1 commit intomasterfrom
yew-link
Open

feat: add yew-link crate for unified SSR/CSR data fetching#4027
Madoshakalaka wants to merge 1 commit intomasterfrom
yew-link

Conversation

@Madoshakalaka
Copy link
Member

@Madoshakalaka Madoshakalaka commented Mar 1, 2026

Description

Closes #2649, whose lower-level half includinguse_prepared_state and use_transitive_state is already shipped

This PR implements the higher-level half of #2649: a yew-link crate that unifies SSR, hydration, and client-side data fetching behind a single hook.

The mentioned lower-level hooks already carry server-computed state to the client during hydration. But after that initial page load, client-side navigation requires a completely separate fetch path. This means every data-dependent component needs two code paths stitched together manually. yew-link closes this gap.

Implemented new crates proposed in the pr: yew-link and yew-link-macro

Overhauled ssr_router example, rewired to use yew-link instead of generating content inline. Demonstrates #[linked_state], LinkProvider, use_linked_state, Resolver::register_linked, and the axum handler, with full SSR-to-hydration state transfer and client-side nav fetching.

See the new website changes to understand the usage pattern

Comparison with Bounce's Query API

@futursolo's Bounce, whose Query API and use_prepared_query hook do overlapping but not identical work

yew-link has bounded cache: client-side cache uses lru::LruCache (default 64 entries, configurable via LinkProvider's cache_capacity prop). Dependency gated to wasm32 only.

yew-link has more granular code isolation as its #[linked_state] strips resolve() from WASM and Query::query() body is always compiled into WASM.

yew-link also provides better utility by its built-in linked_state_handler for axum.

Checklist

  • I have reviewed my own code
  • I have added tests

I have overhauled the ssr_router to use yew-link.

I have overhauled the ssr_router's E2E test too:

  1. Directly visiting a post by its url post/0 receives a server-rendered page with no extra fetch requests.
  2. vist the posts page first, click on the anchor of posts with the id 0, in-app navigation happens and fetch happens exactly once
  3. the post contents in 1 and 2 match

I'm not sure how cargo release in our .github/workflows/publish.yml handles the two added new crates, but that's a matter for the future when when publish yew-link with yew 0.23

github-actions[bot]
github-actions bot previously approved these changes Mar 1, 2026
@github-actions
Copy link

github-actions bot commented Mar 1, 2026

Visit the preview URL for this PR (updated for commit 2c61622):

https://yew-rs--pr4027-yew-link-sylkdib6.web.app

(expires Sat, 21 Mar 2026 12:15:03 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

github-actions[bot]
github-actions bot previously approved these changes Mar 1, 2026
@github-actions
Copy link

github-actions bot commented Mar 1, 2026

Benchmark - SSR

Yew Master

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 270.230 270.680 270.448 0.142
Hello World 10 459.471 469.666 462.216 3.088
Function Router 10 32207.492 32759.571 32568.651 159.895
Concurrent Task 10 1004.685 1007.278 1006.027 0.764
Many Providers 10 1025.768 1066.437 1040.079 12.337

Pull Request

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 257.816 258.116 258.012 0.092
Hello World 10 448.531 451.751 449.527 1.163
Function Router 10 32137.214 32922.217 32667.406 229.506
Concurrent Task 10 1005.113 1006.741 1005.992 0.638
Many Providers 10 1045.255 1075.403 1054.994 9.952

@github-actions
Copy link

github-actions bot commented Mar 1, 2026

Size Comparison

Details
examples master (KB) pull request (KB) diff (KB) diff (%)
async_clock 101.056 101.056 0 0.000%
boids 168.881 168.902 +0.021 +0.013%
communication_child_to_parent 94.511 94.508 -0.003 -0.003%
communication_grandchild_with_grandparent 106.345 106.352 +0.007 +0.006%
communication_grandparent_to_grandchild 102.690 102.691 +0.001 +0.001%
communication_parent_to_child 91.919 91.919 0 0.000%
contexts 106.417 106.412 -0.005 -0.005%
counter 87.231 87.231 0 0.000%
counter_functional 89.267 89.269 +0.002 +0.002%
dyn_create_destroy_apps 91.147 91.147 0 0.000%
file_upload 100.326 100.324 -0.002 -0.002%
function_delayed_input 95.238 95.242 +0.004 +0.004%
function_memory_game 174.100 174.104 +0.004 +0.002%
function_router 395.810 394.940 -0.869 -0.220%
function_todomvc 165.387 165.396 +0.009 +0.005%
futures 235.984 235.981 -0.003 -0.001%
game_of_life 105.532 105.532 0 0.000%
immutable 260.574 260.573 -0.001 -0.000%
inner_html 81.774 81.774 0 0.000%
js_callback 110.396 110.396 -0.001 -0.001%
keyed_list 180.710 180.709 -0.001 -0.001%
mount_point 85.147 85.147 0 0.000%
nested_list 114.095 114.091 -0.004 -0.003%
node_refs 92.521 92.521 0 0.000%
password_strength 1719.354 1719.396 +0.043 +0.002%
portals 93.994 93.995 +0.001 +0.001%
router 366.460 365.598 -0.862 -0.235%
suspense 114.396 114.395 -0.001 -0.001%
timer 89.369 89.369 0 0.000%
timer_functional 99.805 99.807 +0.002 +0.002%
todomvc 143.096 143.096 0 0.000%
two_apps 87.145 87.145 0 0.000%
web_worker_fib 137.050 137.049 -0.001 -0.001%
web_worker_prime 188.234 188.232 -0.002 -0.001%
webgl 83.920 83.922 +0.002 +0.002%

✅ None of the examples has changed their size significantly.

github-actions[bot]
github-actions bot previously approved these changes Mar 1, 2026
@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Benchmark - core

Yew Master

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.125 ns      │ 3.373 ns      │ 2.128 ns      │ 2.181 ns      │ 100     │ 1000000000

Pull Request

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.126 ns      │ 3.374 ns      │ 2.129 ns      │ 2.163 ns      │ 100     │ 1000000000


// -- Part 2: Navigate to /posts within the same app, then to /posts/0 --

yew::scheduler::flush().await;
Copy link
Member Author

Choose a reason for hiding this comment

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

these flush turned out to be very neccessary. Without them the ssr tests fails.

When .click() fires on the element, yew's event delegation hasn't processed it yet (scheduler is deferred to the microtask queue). So the browser's default anchor behavior kicks in - it performs a real navigation to /posts, which crashes the test runner.

@Madoshakalaka
Copy link
Member Author

I've done some external testing too

My work has an SSR Axum-Yew app totaling 40k+ lines of rust. I've patched the project's dependency to use yew-link and migrated a handful of bounce's use_prepared_query to yew-link and saw good code simplification and no test failure.

@Madoshakalaka Madoshakalaka marked this pull request as ready for review March 7, 2026 14:33
github-actions[bot]
github-actions bot previously approved these changes Mar 7, 2026
github-actions[bot]
github-actions bot previously approved these changes Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-yew-link Area: The yew-link crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Send states created during SSR alongside SSR artifact to be used with client-side rendering hydration

1 participant