Skip to content

feat: Decouple rendering from async context #842

Merged
justinvdm merged 1 commit intoredwoodjs:mainfrom
Toolbase-AI:gching/render-to-stream-req-info
Oct 27, 2025
Merged

feat: Decouple rendering from async context #842
justinvdm merged 1 commit intoredwoodjs:mainfrom
Toolbase-AI:gching/render-to-stream-req-info

Conversation

@gching
Copy link
Copy Markdown
Contributor

@gching gching commented Oct 25, 2025

Ref #840

The current rendering functions (renderToStream, renderToString) are tightly coupled to the requestInfo object provided by async local storage. This makes it difficult to use our rendering engine in environments outside defineApp.

This PR decouples the rendering logic from this implicit context.

Changes

  • constructWithDefaultRequestInfo: Introduced and exposes a new utility function in entry from sdk/src/runtime/requestInfo/utils.tsx. This function creates a default RequestInfo object and merging with user-provided overrides.
  • Refactored Rendering Functions: renderToStream and renderToString were updated to accept an optional requestInfo parameter

Before Change

  • renderToStream implicitly depended on the requestInfo from the async local storage context
  • To call these render methods, you had to import the internal runWithRequestInfo function and manually construct a complete RequestInfo object.
  // Would error, since no requestInfo context.
  const html = await renderToString(<Welcome />);


  const requestInfo = {
    // Need to know internals and understand defaults
    // ...
  }

  await runWithRequestInfo(requestInfo, async () => {
    return renderToString(<Welcome />);
  });

After Change

  • Rendering functions now accept an optional requestInfo object
  • Rendering functions automatically build up a default requestInfo and overrides it given the context requestInfo (backwards compatible) if it exists and user provided requestInfo overrides
  • Exposes constructWithDefaultRequestInfo to be used to construct a requestInfo if needed (ie. we can use this in defineApp potentially)
  // Calling it without context won't error, and has defaults
  const html = await renderToString(<Welcome />);

  // Support simple overrides, ie. nonce
  const html = await renderToString(<Welcome />, {requestInfo: {nonce: 'hi-gavin-this-is-random'});

  // Can construct outside if needed in case we need a full requestInfo
  const requestInfo = {
    //...
  }
  // or
  const requestInfo = constructWithDefaultRequestInfo({...})

  // Supports passing requestInfo in it's entirety or utilize context in the past
  const html = await renderToString(<Welcome />, {requestInfo});

  await runWithRequestInfo(requestInfo, async () => {
    return renderToString(<Welcome />);
  });
  

export * from "../render/renderToStream";
export * from "../render/renderToString";
export * from "../requestInfo/types";
export * from "../requestInfo/utils";
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Debating if we should expose this to userland, indifferent tbh

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Wondering same 👍 I think we can keep it exposed and defer documenting it for now.

@@ -0,0 +1,54 @@
import { FC } from "react";
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Decided to make a new file for this since it contains jsx

): RequestInfo => {
const { rw: rwOverrides, ...otherRequestInfoOverrides } = overrides;

const defaultRequestInfo: RequestInfo = {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copied over from defineApp, not sure if these defaults are good though

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Makes sense!

* Constructs a generic requestInfo that can be used as defaults.
* Allows for passing in overrides to initialize with defaults.
*/
export const constructWithDefaultRequestInfo = (
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can potentially use this elsewhere like defineApp

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thinking bout this more, probably just keeping it as getDefaultRequestInfo will be better

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good as is! We likely do want to be doing a custom merge (e.g. allowing forrw overrides) as you're already doing

@gching gching force-pushed the gching/render-to-stream-req-info branch from b3c3baf to 545d862 Compare October 25, 2025 04:24
@gching gching changed the title [feat] Decouple rendering from async context feat: Decouple rendering from async context Oct 25, 2025
waitUntil: () => {},
passThroughOnException: () => {},
props: {},
},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, I guess we need this stubbed this way since the caller wouldn't be providing this, makes sense 👍

@justinvdm
Copy link
Copy Markdown
Collaborator

Closing and re-opening to get CI builds to run, sorry for the noise

@justinvdm justinvdm closed this Oct 27, 2025
@justinvdm justinvdm reopened this Oct 27, 2025
@justinvdm
Copy link
Copy Markdown
Collaborator

This looks great, thank you for taking this on @gching! And I appreciate the detailed context/writeup for the PR description

Just getting the CI builds running now, will merge and release once green

@justinvdm justinvdm merged commit 091ff7b into redwoodjs:main Oct 27, 2025
14 checks passed
@justinvdm
Copy link
Copy Markdown
Collaborator

Released in https://github.com/redwoodjs/sdk/releases/tag/v1.0.0-beta.19

@gching gching deleted the gching/render-to-stream-req-info branch October 28, 2025 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants