[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 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
12/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
领域
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 分钟
30 天内合并 PR
500

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

solidjs/solid 的其他 Issue

查看 solidjs/solid 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。