The way I've been approaching this lately is TypeScript on the frontend, then a thing TypeScript layer on the backend (via Deno), with those two pieces connected with tRPC. The real backend guts are in Rust (or whatever), and the backend tRPC layer talks to the Rust stuff with gRPC.
So something like this:
[(TS web client) <--tRPC--> (TS thin backend)] <--gRPC--> (Rust service)
This is a bit awkward, but honestly worth it for what you get with tRPC. One thing that took some getting used to is with tRPC the line between "client" and "server" gets blurry, which makes me uncomfortable for all sorts of reasons but in practice works well enough to make it not worth worrying about for now.