Serverside Rendering

From SocialStack
Revision as of 18:43, 23 February 2023 by 24.120.54.20 (talk) (→‎UI Requirements)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Often it's useful for react content to be rendered on the serverside. This can be for emails, pre-generated pages for faster load times or for a search indexer. The gateway to using the serverside renderer for custom things is the Api/CanvasRenderer module as it will render any canvas JSON for you.

UI Requirements

When the serverside renderer is in use, you will need to strictly follow standard React side effects guidance. That means anything which attempts to add event handlers or manipulate the DOM must occur in a side effect such as useEffect or componentDidMount. Do not measure the screen or cause any side effects in your render functions, as the serverside renderer does not have a screen or a DOM and will therefore error on you whilst trying to render your component. Side effects (like useEffect) do not run on the server.

Asynchronous content and webRequest

If your component requires some content to be loaded, try to use a graph, <Content> or <Loop> as all of these already consider the serverside renderer and will therefore just work. They are internally overriden by the serverside renderer such that they directly mount your C# API services and thus the server does not actually make a web request to itself. webRequest itself is not supported on the serverside renderer.

If you really must do something asynchronous for your component to load, you'll need a React class component and put the pending promise into the state within the constructor. It doesn't matter what the state field is called - it just needs to be at the root level of the state object. The serverside renderer constructs your component then does a loop over each field in the state and if any are pending promises, it will wait for them to all resolve before then invoking your render function. Just keep in mind that your component needs to also render on the frontend too, so make sure you try it both ways.

Pre-generated pages

To turn on the pre-generated page system, go into the admin panel and edit the configuration for HtmlService. Set PreRender to true.

Custom rendering

Here's an example of how to ask the serverside renderer to render a canvas for you:

// Or use injection:
var _canvasRenderer = Services.Get<CanvasRendererService>();

// Make sure the context you use contains the user, their locale and anything else your UI needs.
// This is just a basic anonymous context which will therefore have the most access restrictions on it.
var context = new Context();

// The pageState tells the renderer things like the apparent URL it is rendering as.
// This is important if your components use things like the primary content.
// If your UI does not care about the URL at all though, you can just create a reusable empty one too.
var pageState = new CanvasRenderer.PageState();

// The canvas JSON to render:
var canvasJson = "{\"s\":\"Hello, world!\"}";

// Ask the renderer to render it:
var rendered = await _canvasRenderer.Render(context, canvasJson, pageState);

// rendered.Body is the resulting HTML

How it works

The serverside renderer loads the frontend JS into a ClearScript V8 engine. For high performance, it instances one javascript scope, meaning all render requests are handled by the same scope and therefore any global value pollution will mix between requests. As all the API services internally require a Context object in order to establish permissions and locale etc, a Context is required by the render call. This context object handle is passed into the javascript scope and then tracked throughout the render request. This tracking enables multiple contextual requests to occur simultaneously in the same scope. Because of the way ClearScript works, C# objects and methods are directly available inside V8. So, requests to get content - such as those made by Loop - are connected directly to the underlying services, such that the renderer does not have to have all the overhead of making requests to itself either.