Skip to content

SSR emits <!--!$--> between adjacent memo/function items even when they resolve to elements #3383

Description

@ryansolid

Version: 2.0.0-rc.8 (@solidjs/web)

Repro

import { renderToString, ssrElement, createComponent } from "@solidjs/web";
import { createMemo } from "solid-js";

const Item = p => createMemo(() => ssrElement("li", {}, () => p.i, true));
renderToString(() => [1, 2, 3].map(i => createComponent(Item, { i })), { noScripts: true });
// <li _hk=00 >1</li><!--!$--><li _hk=10 >2</li><!--!$--><li _hk=20 >3</li>

renderToString(() => [1, 2, 3].map(i => ssrElement("li", {}, () => i, true)), { noScripts: true });
// <li _hk=0 >1</li><li _hk=1 >2</li><li _hk=2 >3</li>

Any component whose return value is a memo/accessor (Dynamic, Show, createMemo(() => <el/>), most wrapper libraries) pays 8 bytes per adjacent instance and an extra comment node the client has to skip in stripTextSeparators. For a 1000-item list that's 8 KB of markup and 1000 comment nodes that exist only to separate text nodes that don't exist.

Cause

packages/web/src/server.ts, tryResolveString (~4456) and resolveSSRNode (~4515): the separator decision is made on the unresolved item — itemNonObj = item !== null && typeof item !== "object" — so functions count as "non-object" (text-like) and two adjacent functions get a separator regardless of what they return.

Expected

The separator is only needed between two adjacent text nodes (so the client can split them). Resolve the item first and decide on the resolved value: string/number → text-like; { t } / element markup → not.

Notes

  • tryResolveString has no top guard (unlike resolveSSRNode), so the root array of renderToString gets separators too.
  • Client side stripTextSeparators / claimInitial cost scales with these; see the hydrate numbers in the perf tracker.
  • Surfaced by the @yak/solid audit (DigitecGalaxus/next-yak#644).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions