You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Remove react-dom/server from the client bundle by building the cytoscape lucide SVG string from Lucide geometry data instead of rendering a React element to markup.
Context
utils/lucideIcons.ts produces the canvas SVG string with:
renderToStaticMarkup is the only reason react-dom/server (~238 KB raw, ~60–80 KB gzipped) is in the client bundle — verified as the sole import across packages/graph-explorer/src. The DOM surfaces no longer need it at all: VertexSymbolIcon renders lucide through DynamicIcon, so only the cytoscape background-image still needs a string.
Every lucide icon module also exports its geometry as plain data:
DynamicIcon itself reads __iconNode, so this is the same data lucide-react renders from.
Why this was deferred
PR #1777 had a hand-rolled buildSvgString + escapeXmlAttr and its review removed it — finding R7, "buildSvgString unvalidated tag interpolation", resolved as "no longer constructing SVG manually". Reintroducing string concatenation reopens a closed finding, which is a conversation this branch did not need to have.
Build via document.createElementNS + XMLSerializer, not string concatenation. With no interpolation there is no injection surface to validate, so R7 does not apply by construction, and XMLSerializer is already used in core/icons/iconImageUrl.ts.
Scope
Pin lucide-react to exact 1.20.0 (currently ^1.20.0) — __iconNode is exported and typed but underscore-prefixed, so treat it as semi-public.
Add a shape-assertion test so a future bump that changes __iconNode fails CI loudly rather than silently rendering blank icons.
Build the canvas SVG from __iconNode + lucide’s defaultAttributes via createElementNS.
Drop renderToStaticMarkup and confirm react-dom/server leaves the bundle.
Notes from investigation
Across all 1985 icon modules in 1.20.0:
Only 7 element types: circle, ellipse, line, path, polygon, polyline, rect. No g.
Zero icons use id, defs, gradients, masks, clipPath, or style, so there is no collision surface.
All icon-level attribute keys are already lowercase. React’s key must be stripped rather than emitted.
camelCase conversion is needed for exactly three wrapper attributes, all from the fixed defaultAttributes: strokeWidth, strokeLinecap, strokeLinejoin. A camelCase attribute emitted into raw SVG is silently ignored by the browser, so this is the main correctness risk.
10 icons set their own fill="currentColor" (vault, tag, tags, chart-scatter, …), so per-element fill must be emitted when present.
Remove
react-dom/serverfrom the client bundle by building the cytoscape lucide SVG string from Lucide geometry data instead of rendering a React element to markup.Context
utils/lucideIcons.tsproduces the canvas SVG string with:renderToStaticMarkupis the only reasonreact-dom/server(~238 KB raw, ~60–80 KB gzipped) is in the client bundle — verified as the sole import acrosspackages/graph-explorer/src. The DOM surfaces no longer need it at all:VertexSymbolIconrenders lucide throughDynamicIcon, so only the cytoscapebackground-imagestill needs a string.Every lucide icon module also exports its geometry as plain data:
DynamicIconitself reads__iconNode, so this is the same data lucide-react renders from.Why this was deferred
PR #1777 had a hand-rolled
buildSvgString+escapeXmlAttrand its review removed it — finding R7, "buildSvgStringunvalidated tag interpolation", resolved as "no longer constructing SVG manually". Reintroducing string concatenation reopens a closed finding, which is a conversation this branch did not need to have.Build via
document.createElementNS+XMLSerializer, not string concatenation. With no interpolation there is no injection surface to validate, so R7 does not apply by construction, andXMLSerializeris already used incore/icons/iconImageUrl.ts.Scope
lucide-reactto exact1.20.0(currently^1.20.0) —__iconNodeis exported and typed but underscore-prefixed, so treat it as semi-public.__iconNodefails CI loudly rather than silently rendering blank icons.__iconNode+ lucide’sdefaultAttributesviacreateElementNS.renderToStaticMarkupand confirmreact-dom/serverleaves the bundle.Notes from investigation
Across all 1985 icon modules in 1.20.0:
circle,ellipse,line,path,polygon,polyline,rect. Nog.id,defs, gradients, masks,clipPath, orstyle, so there is no collision surface.keymust be stripped rather than emitted.defaultAttributes:strokeWidth,strokeLinecap,strokeLinejoin. A camelCase attribute emitted into raw SVG is silently ignored by the browser, so this is the main correctness risk.fill="currentColor"(vault,tag,tags,chart-scatter, …), so per-elementfillmust be emitted when present.Out of scope
Recoloring custom SVG icons — tracked in #2105.
Related
Follows the icon pipeline rework in #2102. Background in
docs/adr/20260813-icon-registry-not-react-query.md(see the final consequence).Important
Internal only — this issue is maintained by the core team and is not accepting external contributions.