Skip to content

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

Open
Madoshakalaka wants to merge 2 commits intomasterfrom
yew-link
Open

feat: add yew-link crate for unified SSR/CSR data fetching#4027
Madoshakalaka wants to merge 2 commits 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 f92053d):

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

(expires Wed, 25 Mar 2026 11:27:10 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 291.214 291.988 291.512 0.281
Hello World 10 508.930 517.649 511.485 2.782
Function Router 10 31822.253 32811.588 32212.470 349.328
Concurrent Task 10 1006.547 1007.780 1007.249 0.426
Many Providers 10 1049.824 1071.616 1059.972 7.000

Pull Request

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 310.697 312.336 311.192 0.681
Hello World 10 485.232 499.397 489.343 4.074
Function Router 10 31532.614 32384.471 31949.893 232.827
Concurrent Task 10 1005.327 1007.812 1007.096 0.744
Many Providers 10 1072.584 1101.322 1086.480 9.448

@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.095 101.096 +0.001 +0.001%
boids 168.882 168.902 +0.021 +0.012%
communication_child_to_parent 94.509 94.513 +0.004 +0.004%
communication_grandchild_with_grandparent 106.351 106.350 -0.001 -0.001%
communication_grandparent_to_grandchild 102.692 102.689 -0.003 -0.003%
communication_parent_to_child 91.921 91.921 0 0.000%
contexts 106.411 106.414 +0.003 +0.003%
counter 87.231 87.232 +0.001 +0.001%
counter_functional 89.269 89.270 +0.001 +0.001%
dyn_create_destroy_apps 91.146 91.147 +0.001 +0.001%
file_upload 100.326 100.324 -0.002 -0.002%
function_delayed_input 95.241 95.240 -0.001 -0.001%
function_memory_game 174.099 174.112 +0.014 +0.008%
function_router 395.915 395.744 -0.171 -0.043%
function_todomvc 165.385 165.395 +0.010 +0.006%
futures 235.984 235.982 -0.002 -0.001%
game_of_life 105.532 105.533 +0.001 +0.001%
immutable 260.494 260.505 +0.011 +0.004%
inner_html 81.774 81.775 +0.001 +0.001%
js_callback 110.397 110.400 +0.003 +0.003%
keyed_list 180.710 180.707 -0.003 -0.002%
mount_point 85.147 85.147 0 0.000%
nested_list 114.091 114.095 +0.004 +0.003%
node_refs 92.521 92.521 +0.001 +0.001%
password_strength 1719.354 1719.396 +0.042 +0.002%
portals 93.992 93.994 +0.002 +0.002%
router 366.558 366.400 -0.157 -0.043%
suspense 114.395 114.402 +0.008 +0.007%
timer 89.369 89.370 +0.001 +0.001%
timer_functional 99.803 99.807 +0.004 +0.004%
todomvc 143.096 143.097 +0.001 +0.001%
two_apps 87.145 87.146 +0.001 +0.001%
web_worker_fib 137.045 137.046 +0.001 +0.001%
web_worker_prime 188.227 188.235 +0.009 +0.005%
webgl 83.920 83.921 +0.001 +0.001%

✅ 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.175 ns      │ 2.296 ns      │ 2.179 ns      │ 2.181 ns      │ 100     │ 1000000000

Pull Request

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.125 ns      │ 2.198 ns      │ 2.128 ns      │ 2.131 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