Which IPC transport should a Tauri 2 app standardize on for paginating a large dataset to the webview?
0 reputation · 14 Nov 2025, 09:06 UTC
0 reputation · 14 Nov 2025, 09:06 UTC
I'm designing the data layer for a Tauri 2 desktop app where the webview needs to browse a large local dataset (tens of thousands of rows) in bounded pages. The documented options all seem viable: a #[tauri::command] that returns a bounded slice via invoke(), event-based streaming from the backend, or a Channel that delivers chunks incrementally.
The constraints I care about: results must arrive fast enough that scrolling feels instant, the Rust side holds its connection in tauri::State, and I want to avoid one large JSON serialization blocking the webview. The documentation presents commands, events, and Channel without prescribing one for bulk reads, and I know JSON serialization of large payloads can dominate latency.
My questions:
invoke() per page generally sufficient, or does the serialization overhead justify Channel-based chunking even at that size?Assume Tauri v2 with the matching @tauri-apps/api; I have not benchmarked yet, so rule-of-thumb guidance plus what to measure is welcome.
35775 reputation · 14 Nov 2025, 10:25 UTC
Standardize on a single #[tauri::command] per page, invoked with a cursor or offset, returning a bounded slice. At ~100 rows per page this is almost always fast enough for instant scrolling, and it keeps your data layer simple: one request, one response, natural error handling, and trivial cancellation (just stop invoking). Reserve Channel for the cases where measurement — not anticipation — shows a page payload large enough to stall the webview's JSON parsing.
The latency you fear is dominated by two things: serializing the payload in Rust and parsing the JSON string on the webview's main thread. For a page of 100 typical rows, the serialized JSON is usually in the low tens of kilobytes, and both serialization and parsing complete in single-digit milliseconds on modern hardware — well inside a 16 ms frame budget. Chunking that payload across a Channel adds coordination complexity without removing work you can actually perceive.
A rule of thumb worth treating as a hypothesis, not a law: if a page's serialized size stays under roughly 50 KB and your measured invoke round-trip is comfortably under one frame, a command is sufficient. Past that point — wide rows, embedded text blobs, denormalized joins — the single monolithic JSON.parse on the JS thread becomes the long task that drops frames, and incremental delivery starts paying for itself.
Channel is an async, ordered stream from Rust to the webview. Compared to events it gives you two things that matter for incremental rendering:
That makes Channel the right tool for unbounded or very large result sets — "stream all matches" rather than "fetch page N" — where you render rows as they arrive. The costs are real: you must drive the stream on both sides, handle early termination (user scrolls away, filters change), and clean up the channel when the webview navigates or is destroyed, or you leak the Rust-side sender.
No. Tauri 2's documentation presents commands, events, and Channel without prescribing a payload cutoff for bulk reads. Any specific number — including the ~50 KB guideline above — is a heuristic to validate against your own schema and hardware, not a contract from the framework. Treat it as a starting point for benchmarking, and re-check it if you upgrade Tauri or change webview runtimes.
JSON.stringify(page).length in the webview for your largest realistic page.performance.now() timestamps; separately measure time from response received to rows rendered.If those numbers stay healthy at your target page size, ship the command-based design and keep Channel in your back pocket for the "export everything" or live-tail features where streaming is genuinely the right shape.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.