[2.0 rc.20] SSR: concurrent renders sharing a lazy() component's first load read another render's async memo value

クローズ
#4,595 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
12/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
javascript, typescript
領域
frontend

調査の方向性

Start with the repro gist and its run.mjs, which runs the concurrent renders and prints the per-render tag results. The bug is in the lazy() resolution path, so compare it with the async memo retry fix referenced as #3983, which covers a different path. Done means the gist prints ok for the lazy, no Loading, awaited case with each render showing its own tag.

索引モデルが issue の本文から書いたものです。

説明

Describe the bug

Concurrent server renders that share the first load of a module-level lazy() component read one render's async memo value in every other render. Six renders of the same app, each with its own tag, all print the first render's memo value:

LEAK  lazy, no Loading, awaited      a:a b:a c:a d:a e:a f:a

Each page prints <its tag>:<the tag its async memo resolved to>. The memo in render b is created with b and resolves to { tag: "b" }, yet render b's HTML shows a.

It takes all of these together:

  • a module-level lazy() component, whose first load the concurrent renders share (as a router's lazy route is on a cold server)
  • an async createMemo inside it
  • no <Loading> above it
  • await renderToStream(...), the promise form

Each of these controls passes: the same component rendered eagerly, wrapped in <Loading>, streamed through .pipe(), or with the lazy component loaded once before the concurrent renders.

This looks like the same class as #3972 (an async memo's retry running under another concurrent render's context, fixed in #3983), reached through the lazy() resolution path, which that fix doesn't cover. It still reproduces on 2.0.0-rc.20.

Impact. A server that renders concurrent requests in one process, for example a Cloudflare Worker isolate or a Node server, shows one request's async data in another request's HTML whenever a lazy route's first load is shared. We hit it as prerendered pages in four locales showing a fifth locale's messages. For per-user data, it would show one user's data to another.

Your Example Website or App

https://gist.github.com/lovrozagar/ee7c5e9af6f593a98923595a658d3318

Steps to Reproduce the Bug or Issue
  1. Download the gist into a folder.
  2. bun install (or npm install).
  3. bun run repro, which runs vite build && node run.mjs and exits 1 on a leak.
Expected behavior

Every render shows its own memo value:

ok    lazy, no Loading, awaited      a:a b:b c:c d:d e:e f:f
Screenshots or Videos

Full output on 2.0.0-rc.20:

LEAK  lazy, no Loading, awaited      a:a b:a c:a d:a e:a f:a
ok    eager, no Loading, awaited     a:a b:b c:c d:d e:e f:f
ok    lazy, in Loading, awaited      a:a b:b c:c d:d e:e f:f
ok    lazy, no Loading, piped        a:a b:b c:c d:d e:e f:f
ok    lazy already loaded, awaited   a:a b:b c:c d:d e:e f:f
Platform
  • solid-js / @solidjs/web / @solidjs/signals: 2.0.0-rc.20 (same output on rc.18 and rc.19)
  • @solidjs/vite-plugin: 3.0.0-next.55 (rc.18 run: 3.0.0-next.51)
  • vite: 8.3.5
  • Node: 26.12.1, Linux
Additional context

In a real app the leaked value comes from the router's lazy route. A minimal version with @solidjs/router (createRouter({ routes: [{ path: "/:tag", component: lazy(...) }] }), rendered with no <Loading> above the router) shows the same pattern: the route params are right, but the memo's value is the first render's.

主要言語
TypeScript
スター
36.2k
フォーク
1.1k
平均マージ
10時間 39分
マージ済み PR(30日)
500

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

solidjs/solid のほかの issue

solidjs/solid の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。