镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(LiveProvider): add opt-in server-rendered previews - #414

Open
tujoworker wants to merge 4 commits into
FormidableLabs:masterfrom
tujoworker:feat/ssr
Open

tujoworker wants to merge 4 commits into
FormidableLabs:masterfrom
tujoworker:feat/ssr

Conversation

@tujoworker

@tujoworker tujoworker commented May 18, 2026 •

Copy link
Copy Markdown

Description

Documentation pages using LiveProvider currently send an empty preview in their initial HTML, even for static examples. This makes that content depend on downloading and running JavaScript. Related to #396 and the earlier initial-render work in #321/#322.

This adds <LiveProvider ssr> for trusted, deterministic examples. For code="<strong>Hello from SSR</strong>", the server output changes from <div></div> to <div><strong>Hello from SSR</strong></div>. Existing consumers stay client-only because ssr defaults to false.

Synchronous inline and noInline previews render immediately. Async transforms leave the server preview empty and reuse the client initialization result during hydration. Delayed noInline callbacks still update the client preview. Transform/evaluation failures use LiveError, and obsolete async results are ignored. Preview component render errors retain React's normal SSR behavior; the docs explain the server-safe code requirements.

The runnable SSR example prints and asserts both HTML outputs.

Type of change

  • Backward-compatible feature, with a minor changeset

Review checklist

  • Rebased onto current upstream master (v5.0.1)
  • Complete diff self-reviewed
  • Existing client-only defaults and delayed rendering preserved
  • Regression tests demonstrated the delayed-render and async-error failures before their fixes
  • SSR, hydration, StrictMode, failure/recovery, updates, and stale results covered
  • Public API and SSR limitations documented
  • No new lint warnings; existing Editor warnings remain
  • Builds, typechecks, 77 unit tests, and 37 Chromium tests pass with React 19.3.0 and React 18.0.0

Public validation run uses the unchanged upstream CI workflow against this exact commit. Upstream CI still requires maintainer approval; the fork run provides inspectable evidence while that is pending.

@vercel

vercel Bot commented May 18, 2026

Copy link
Copy Markdown

@tujoworker is attempting to deploy a commit to the formidable-labs Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dae0d9a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
react-live Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@tujoworker tujoworker changed the title Add initial SSR support for LiveProvider previews feat(LiveProvider): add opt-in server-rendered previews Sep 24, 2026
@tujoworker

Copy link
Copy Markdown
Author

Hi @ryan-roemer. This is now rebased onto v5 and reworked as opt-in SSR through an ssr prop, disabled by default. It includes hydration, error, update, and async race coverage, plus a changeset. The full Node 24 build and test matrix passes locally.

GitHub Actions is waiting for maintainer approval because this is an external contribution, and Vercel also needs team authorization. Would you have time to approve CI and review the API direction? Feedback on whether this approach fits the project would also be very helpful.

@tujoworker

Copy link
Copy Markdown
Author

Hi @ilteoood, would opt-in initial SSR through <LiveProvider ssr> fit the project's direction? It keeps existing consumers client-only, while static documentation examples can appear in the initial HTML.

I've rebased this onto v5.0.1, added a runnable before/after example, and strengthened coverage for hydration, StrictMode, delayed rendering, and error recovery. React 18.0.0 and 19.3.0 both pass locally, and the public fork CI run uses the upstream workflow. The description now includes the rationale and review checklist.

An API-direction check would help before a full review. If this is a direction you'd accept, could you also approve the upstream CI run? Thanks.

@tujoworker

Copy link
Copy Markdown
Author

If anyone is interested in SSR support, I could publish a separate adapter package you can use with the original react-live dependency, without patching it.

We're taking this adapter approach internally for now while we wait for upstream SSR support. Here is the implementation PR: dnbexperience/eufemia#9739

@ilteoood

Copy link
Copy Markdown

Hi @tujoworker, feel free to open a PR and we'll review it asap

});
// StrictMode invokes the client initializer twice; the server invokes it once.
expect(transformCode).toHaveBeenCalledTimes(3);
expect(container.textContent).toBe("hello");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of checking it in this way, could we use a snapshot tests instead?
this allows us to check that the rendered html contains also the strong tag

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, switched this to an HTML snapshot so we check the tag too.

await act(async () => {
root = hydrateRoot(container, preview);
});
expect(container.querySelector("pre").textContent).toBe(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this one too, including the

 markup.

</LiveProvider>,
);
});
expect(container.querySelector("strong").textContent).toBe("fixed");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, the snapshot now covers the recovered preview and confirms the error is gone.

</LiveProvider>,
);

expect(html).toBe("<div><strong>Hello SSR!</strong></div>");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would apply it to other html checks as well

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied snapshots to all the HTML checks in this file.

Comment on lines +205 to +207
await act(async () => {
resolvers.forEach((resolve) => resolve("<strong>Hello SSR!</strong>"));
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we are not awaiting anything inside the act function, so it shouldn't be async at all.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed async here. Still awaiting act() so the preview settles before the snapshot.

Comment on lines +182 to +184
new Promise((resolve) => {
resolvers.push(resolve);
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we simplify this using Promise.withResolvers?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, switched both deferred-promise setups to Promise.withResolvers().

scope,
code: "capture(() => render(<strong>old</strong>))",
});
await act(async () => {});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this empty act?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was flushing the effect so capture had assigned the callback before rerendering. Replaced it with an explicit waitFor on capture.

</LiveProvider>,
);
await screen.findByText("new");
act(() => complete());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
act(() => complete());
act(complete);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied, thanks.

@tujoworker

Copy link
Copy Markdown
Author

Thanks for the good review, @ilteoood ❤️ I've pushed the updates. Could you approve the pending CI run and authorize Vercel when you have a moment? The checks haven't run yet, so it would be good to get them going and see this turn green.

@tujoworker

Copy link
Copy Markdown
Author

Thanks for running the checks, @ilteoood. The React 18 job was ignoring the lockfile and picking up a declaration bundler that doesn't support TypeScript 7.0. I've pushed dae0d9a to preserve the locked tooling and keep React and its types aligned for shared dependencies. Builds, typechecks, 77 unit tests and 37 browser tests pass locally on both React 18 and 19. Could you approve the new CI run when you have a moment?

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants