Skip to content

SSR with Deno (via deno_rider)#212

Open
bu6n wants to merge 5 commits into
woutdp:masterfrom
bu6n:ssr-deno-rider
Open

SSR with Deno (via deno_rider)#212
bu6n wants to merge 5 commits into
woutdp:masterfrom
bu6n:ssr-deno-rider

Conversation

@bu6n

@bu6n bu6n commented Jun 5, 2026

Copy link
Copy Markdown

We have been using a similar setup in production since almost a year without a hitch. I figured out it would be nice to upstream it.

Example project can be migrated with (ran inside ./example_project):
mix live_svelte.migrate --ssr-node-to-deno

and back:
mix live_svelte.migrate --ssr-deno-to-node

Example project can be started with Deno with:

SECRET_KEY_BASE=dlIjx9m2ZEMDbAAiwd2GQs1i+Zfejx2jq70LFm23S5Eg8STAH6erJHRBicukuPET MIX_ENV=prod DATABASE_PATH=example_prod.db PHX_HOST=localhost PHX_SCHEME=http mix phx.server

You can then test SSR in http://localhost:4000/live-ssr.

There's 1 small caveat:

The precompiled deno_rider binaries require glibc >= 2.38 on Linux. This may not be available on older distributions (e.g. Ubuntu 22.04 ships glibc 2.35) or in older docker images. Check your version with ldd --version.

There's a benchmark that can be ran with mix run bench/ssr_benchmark.exs. It's the first time I write such a benchmark, so take it with a grain of salt. Here's the output on my computer:

$ mix run bench/ssr_benchmark.exs
Generated live_svelte app
[bench] Using example_project server.js at ./example_project/priv/svelte
[bench] Detected 8 logical cores (default NodeJS pool_size)
[bench] DenoRider started
[bench] NodeJS supervisor started (pool_size: 8)

========================================================================
  Part 1: Per-Scenario SSR Latency — Deno vs NodeJS (single render)
========================================================================

  Note: Benchee drives one render at a time, so NodeJS pool_size does
  NOT matter here — this is pure single-render latency. See Part 2 for
  how pool_size affects throughput under concurrency.

  Benchee memory_time measures BEAM heap allocation via GC tracing.
  V8/Deno process memory is NOT included. See Part 3 for runtime memory snapshots.


--- hello_world (HelloWorld) ---

Benchmarking Deno ...
Benchmarking NodeJS ...
Calculating statistics...
Formatting results...

Name             ips        average  deviation         median         99th %
Deno         25.30 K       39.53 μs    ±77.53%       34.71 μs       80.21 μs
NodeJS        1.21 K      828.73 μs    ±14.86%      797.54 μs     1325.63 μs

Comparison:
Deno         25.30 K
NodeJS        1.21 K - 20.97x slower +789.21 μs

Memory usage statistics:

Name      Memory usage
Deno           1.07 KB
NodeJS         1.19 KB - 1.11x memory usage +0.117 KB

**All measurements for memory usage were the same**

--- ssr_demo (SsrDemo) ---

Benchmarking Deno ...
Benchmarking NodeJS ...
Calculating statistics...
Formatting results...

Name             ips        average  deviation         median         99th %
Deno         21.48 K       46.56 μs    ±78.42%       42.71 μs       88.50 μs
NodeJS        1.20 K      834.60 μs    ±12.58%      803.17 μs     1286.44 μs

Comparison:
Deno         21.48 K
NodeJS        1.20 K - 17.93x slower +788.04 μs

Memory usage statistics:

Name      Memory usage
Deno           1.09 KB
NodeJS         1.42 KB - 1.31x memory usage +0.34 KB

**All measurements for memory usage were the same**

--- static_component (Static) ---

Benchmarking Deno ...
Benchmarking NodeJS ...
Calculating statistics...
Formatting results...

Name             ips        average  deviation         median         99th %
Deno         20.59 K       48.58 μs    ±79.21%       44.21 μs       96.33 μs
NodeJS        1.18 K      847.60 μs    ±14.91%      807.79 μs     1435.91 μs

Comparison:
Deno         20.59 K
NodeJS        1.18 K - 17.45x slower +799.02 μs

Memory usage statistics:

Name      Memory usage
Deno           1.48 KB
NodeJS         2.16 KB - 1.46x memory usage +0.68 KB

**All measurements for memory usage were the same**

--- simple_counter (SimpleCounter) ---

Benchmarking Deno ...
Benchmarking NodeJS ...
Calculating statistics...
Formatting results...

Name             ips        average  deviation         median         99th %
Deno         18.07 K       55.36 μs   ±150.04%       51.13 μs       96.38 μs
NodeJS        1.17 K      852.96 μs    ±16.79%      817.92 μs     1517.11 μs

Comparison:
Deno         18.07 K
NodeJS        1.17 K - 15.41x slower +797.61 μs

Memory usage statistics:

Name      Memory usage
Deno           1.44 KB
NodeJS         2.22 KB - 1.54x memory usage +0.78 KB

**All measurements for memory usage were the same**

--- struct_display (Struct) ---

Benchmarking Deno ...
Benchmarking NodeJS ...
Calculating statistics...
Formatting results...

Name             ips        average  deviation         median         99th %
Deno         19.79 K       50.53 μs    ±77.72%       47.29 μs       90.72 μs
NodeJS        1.17 K      853.44 μs    ±18.72%      817.36 μs     1752.76 μs

Comparison:
Deno         19.79 K
NodeJS        1.17 K - 16.89x slower +802.91 μs

Memory usage statistics:

Name      Memory usage
Deno           2.84 KB
NodeJS         2.41 KB - 0.85x memory usage -0.42188 KB

**All measurements for memory usage were the same**

--- log_list_50 (LogList) ---

Benchmarking Deno ...
Benchmarking NodeJS ...
Calculating statistics...
Formatting results...

Name             ips        average  deviation         median         99th %
Deno          9.70 K      103.14 μs    ±73.40%       97.58 μs      150.75 μs
NodeJS        1.06 K      946.58 μs    ±23.15%      897.61 μs     2283.99 μs

Comparison:
Deno          9.70 K
NodeJS        1.06 K - 9.18x slower +843.44 μs

Memory usage statistics:

Name      Memory usage
Deno          54.67 KB
NodeJS        31.41 KB - 0.57x memory usage -23.26563 KB

**All measurements for memory usage were the same**

========================================================================
  Part 2: Concurrent Throughput — Deno vs NodeJS pool_size ∈ [4, 8, 12, 16]
  Component : LogList
  Burst     : 2000 renders all submitted simultaneously
  (NodeJS: pool_size is the concurrency limit via poolboy max_overflow=0)
========================================================================

[bench] Sanity check NodeJS ... 1332µs, 1832 chars of HTML
[bench] Sanity check Deno   ... 341µs, 1832 chars of HTML

[bench] Warming up Deno ... done
[bench] Measuring Deno (10 rounds) ... done
[bench] NodeJS supervisor started (pool_size: 4)

[bench] Warming up NodeJS pool=4 ... done
[bench] Measuring NodeJS pool=4 (10 rounds) ... done
[bench] NodeJS supervisor started (pool_size: 8)

[bench] Warming up NodeJS pool=8 ... done
[bench] Measuring NodeJS pool=8 (10 rounds) ... done
[bench] NodeJS supervisor started (pool_size: 12)

[bench] Warming up NodeJS pool=12 ... done
[bench] Measuring NodeJS pool=12 (10 rounds) ... done
[bench] NodeJS supervisor started (pool_size: 16)

[bench] Warming up NodeJS pool=16 ... done
[bench] Measuring NodeJS pool=16 (10 rounds) ... done

========================================================================
  Results  (2000 concurrent renders of LogList)
========================================================================

  runtime                  ops/sec       median ms         best ms
  ----------------------------------------------------------------
  Deno                     30532.5            65.5            61.8
  NodeJS pool=4             2829.0           707.0           603.7
  NodeJS pool=8             3274.2           610.8           582.4
  NodeJS pool=12            3528.6           566.8           534.5
  NodeJS pool=16            3650.9           547.8           535.3


========================================================================
  Part 3: Runtime Memory Snapshot
  (after 100 warmup renders of LogList)
========================================================================
[bench] NodeJS supervisor started (pool_size: 8)

[bench] Warming up NodeJS (100 renders) ... done
[bench] Querying NodeJS memory (8 workers) ... ok
[bench] Warming up Deno (100 renders) ... done
[bench] Querying Deno memory ... ok

========================================================================
  Runtime Memory (V8/Deno process heap)
  NodeJS: per-worker avg + total (8 processes)
  Deno: single process
========================================================================

  runtime                      rss        heapUsed       heapTotal
  ----------------------------------------------------------------
  NodeJS (per)            47.92 MB         5.16 MB         7.48 MB
  NodeJS (total)         383.34 MB        41.31 MB        59.83 MB
  Deno                   231.14 MB        15.87 MB        48.16 MB

Agents were involved, but overall I think it shows good things for Deno.

If this is upstreamed, we may want to make the nodejs dep optional, but this is a breaking change.

bu6n added 5 commits June 5, 2026 10:15
deno_rider is declared as an optional dependency (~> 0.2.0).

Changes:
- lib/ssr/deno.ex: new adapter implementing @behaviour LiveSvelte.SSR
- test/ssr_deno_test.exs: behaviour, NotConfigured error, and server_path/0 coverage
- mix.exs: {:deno_rider, \"~> 0.2.0\", optional: true}
- guides/ssr.md: Deno mode section with glibc >= 2.38 compatibility note
- guides/configuration.md: ssr_module table updated to list Deno as third option
- CHANGELOG.md: Unreleased entry
Adds bench/ssr_benchmark.exs which:
- Writes a self-contained fixture server.js (no Svelte build required)
- Starts NodeJS and DenoRider supervisors programmatically
- Runs Benchee across four scenarios: simple props, nested object, large list, slots
- Captures telemetry :stop durations in parallel for p50/p95/p99 reporting

Adds benchee ~> 1.3 as a dev dependency.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant