Skip to content

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

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

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

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

(expires Sat, 14 Mar 2026 17:32:40 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 310.814 312.194 311.215 0.503
Hello World 10 476.208 558.751 486.268 25.703
Function Router 10 33564.790 34401.247 34058.828 251.073
Concurrent Task 10 1005.712 1010.019 1007.411 1.055
Many Providers 10 1090.942 1134.014 1112.022 16.866

Pull Request

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 310.881 326.083 312.580 4.747
Hello World 10 470.182 482.226 472.702 3.610
Function Router 10 33566.539 34482.172 34002.710 320.369
Concurrent Task 10 1006.372 1007.562 1007.111 0.403
Many Providers 10 1055.180 1084.375 1067.169 10.023

@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 100.272 100.272 0 0.000%
boids 168.092 168.092 0 0.000%
communication_child_to_parent 93.487 93.487 0 0.000%
communication_grandchild_with_grandparent 105.264 105.264 0 0.000%
communication_grandparent_to_grandchild 101.606 101.606 0 0.000%
communication_parent_to_child 90.897 90.897 0 0.000%
contexts 105.172 105.172 0 0.000%
counter 86.282 86.282 0 0.000%
counter_functional 88.275 88.275 0 0.000%
dyn_create_destroy_apps 90.322 90.322 0 0.000%
file_upload 99.377 99.377 0 0.000%
function_delayed_input 94.376 94.376 0 0.000%
function_memory_game 172.944 172.944 0 0.000%
function_router 406.546 406.546 0 0.000%
function_todomvc 164.161 164.161 0 0.000%
futures 235.191 235.191 0 0.000%
game_of_life 104.732 104.732 0 0.000%
immutable 255.899 255.899 0 0.000%
inner_html 80.803 80.803 0 0.000%
js_callback 109.399 109.399 0 0.000%
keyed_list 179.744 179.744 0 0.000%
mount_point 84.175 84.175 0 0.000%
nested_list 113.068 113.068 0 0.000%
node_refs 91.523 91.523 0 0.000%
password_strength 1728.855 1728.855 0 0.000%
portals 93.036 93.036 0 0.000%
router 377.116 377.116 0 0.000%
suspense 113.500 113.500 0 0.000%
timer 88.641 88.641 0 0.000%
timer_functional 98.878 98.878 0 0.000%
todomvc 142.114 142.114 0 0.000%
two_apps 86.176 86.176 0 0.000%
web_worker_fib 136.152 136.152 0 0.000%
web_worker_prime 187.383 187.383 0 0.000%
webgl 83.224 83.224 0 0.000%

✅ 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.091 ns      │ 2.123 ns      │ 2.093 ns      │ 2.097 ns      │ 100     │ 1000000000

Pull Request

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.092 ns      │ 3.347 ns      │ 2.11 ns       │ 2.594 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.

CacheKey previously stored only a u64 hash of the input, so two
different inputs producing the same hash would silently return wrong
cached data. Store the actual input as Rc<dyn Any> with a monomorphized
function pointer for type-erased PartialEq comparison. Hash-based bucket
lookup is unchanged; Eq now verifies with real input equality.
@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
@Madoshakalaka Madoshakalaka requested a review from futursolo March 7, 2026 14:34
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