Skip to content

[2.0 rc.13] Hydrating Loading around lazy moves SSR nodes and loses input focus #3749

Description

@HitoshiroKyoka

Describe the bug

An input focused before hydration loses focus when a lazy() component is inside Loading. References taken to the server-rendered input and its containing main before hydration still point to the connected nodes afterwards, and the typed value is preserved, but the connected main is passed to insertBefore again. Hydration finishes and the counter works. Removing Loading, or replacing the lazy component with the static component, preserves focus.

Your Example Website or App

https://github.com/HitoshiroKyoka/solid2-rc13-loading-repros/tree/main/repro-a

Steps to Reproduce the Bug or Issue

  1. Run npx vite --host 127.0.0.1 --port 4100 --strictPort.
  2. Open http://127.0.0.1:4100/. Within five seconds, focus the SSR input and start typing.
  3. Keep typing as hydration finishes: the input loses focus and stops receiving text. Clicking the counter afterwards changes it to Count: 1.
  4. Remove <Loading> from App.tsx (return <Page />), or replace the lazy declaration with import Page from './Page';. Reload and repeat: focus stays in the input.

Expected behavior

Hydration should preserve the focused SSR input when adopting the same DOM nodes.

Screenshots or Videos

No response

Platform

Linux (Debian 13), Node 24.18.0; Chromium 153.0.8010.12. solid-js / @solidjs/web 2.0.0-rc.13, @solidjs/vite-plugin 3.0.0-next.47, Vite 8.3.2; development SSR.

Additional context

The repository also has ?mode=no-loading and ?mode=no-lazy controls and an automated check. A trimmed version with only the failing case is inline below.

In an empty directory, use Node 22.12+ and run:

npm init -y && npm pkg set type=module
npm i solid-js@2.0.0-rc.13 @solidjs/web@2.0.0-rc.13 && npm i -D @solidjs/vite-plugin@3.0.0-next.47 vite@8.3.2
mkdir src

Write these files:

vite.config.ts

import { defineConfig } from 'vite';
import solid from '@solidjs/vite-plugin';

export default defineConfig({ plugins: [
  solid({ ssr: true, start: { renderMode: 'async' } }),
  { name: 'delay-hydration', configureServer(server) {
    server.middlewares.use((req, _res, next) => {
      // Give the user time to focus the server-rendered input.
      if (req.url?.includes('solid-ssr-entry-client')) setTimeout(next, 5000);
      else next();
    });
  } },
] });

src/App.tsx

import { lazy, Loading } from 'solid-js';
const Page = lazy(() => import('./Page'), undefined, 'src/Page.tsx');
export default function App() {
  return <Loading fallback="Loading..."><Page /></Loading>;
}

src/Page.tsx

import { createSignal } from 'solid-js';
export default function Page() {
  const [count, setCount] = createSignal(0);
  return <><header>Header</header><main><input aria-label="Type before hydration" placeholder="Type here within 5 seconds" /><button onClick={() => setCount(count() + 1)}>Count: {count()}</button></main></>;
}

I reproduced this locally by following the steps above. The investigation and the reduction were done with AI assistance (Claude Code and Codex); I checked the reproduction and the control case by hand before posting.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions