It is a core principle of UIX that code can be used interchangeably on the frontend and the backend. This means that most components and JSX element definitions can either be created either on the frontend (commonly known as 'client-side rendering') or on the backend (commonly known as 'server-side rendering').
This chapter explains the different rendering methods for server-side rendering (which are mostly interchangeable).
Note
The rendering modes discussed in this chapter are not supported for frontend (client-side) rendering. Using the rendering modes on the frontend doesn't introduce any compatibility issues, but it does affect performance and loading times.
UIX distinguishes between five rendering methods on the backend:
With the exception of static rendering, all of the rendering methods provide reactive elements.
Static, backend, and hybrid rendering pre-render the UI on the backend and send it to the frontend as pure HTML.
Hybrid rendering is the default method used for server-side rendering.
When using hybrid rendering, the entire DOM is prerendered on the backend and served as HTML. Once the frontend runtime is initialized, the content is hydrated and becomes responsive.
Hybrid rendering behaves similarly to backend rendering, with the only difference that with backend rendering, the UIX library and DATEX runtime are not loaded on the frontend.
Example:
const counter = $(0);
export default
<div>
Counter: {counter}
<button onclick={() => counter.val++}>Increment counter</button>
<button onclick:frontend={() => alert("Hello")}>Show alert</button>
</div>;
When a component is rendered using hybrid rendering, the onConstruct
and onCreate
lifecycle methods are called in the backend.
If onDisplay
is marked with @frontend
, it will be called as soon as the component is available on the frontend. Without the @frontend
decorator, onDisplay
is never called.
@template()
export class ButtonComponent extends Component {
onCreate() {
console.log('created') // called on the backend
}
@frontend onDisplay() {
console.log('displayed') // called on the frontend
}
}
The frontend rendering mode can be used to render parts of the UI only on the frontend.
Note
If you want to render a page completely on the frontend, we recommend that you just put the page in the frontend entrypoint. The frontend rendering mode only makes sense if you want to render parts of the UI in the backend, and hydrate other sections with > frontend-rendered content.
Simple Example:
import { renderFrontend } from "uix/base/render-methods.ts";
export default
<div>
<h1>My Homepage</h1>
<section>Welcome to my homepage</section>
{
renderFrontend(() => <MyComplexDynamicComponent/>) // this component is rendered on the frontend
}
</div>;
You can also pass placeholder content that is immediately displayed until the frontend rendering is finished:
import { renderFrontend } from "uix/base/render-methods.ts";
export default
<div>
<h1>My Homepage</h1>
<section>Welcome to my homepage</section>
{
renderFrontend(
() => <MyComplexDynamicComponent/>, // this component is rendered on the frontend
<div>Loading...</div> // placeholder content
)
}
</div>;
The frontend rendering function is a transferable function that is
transfered to the frontend context.
This means that any variables needed from the parent scope must be explicitly declared with a use()
statement.
Example:
import { renderFrontend } from "uix/base/render-methods.ts";
const counter = $(0);
export default
<div>
<button onclick={()=>counter.val++}>Increment Counter</button>
{renderFrontend(() => {
use (counter); // use 'counter' from parent scope
return <div>Counter : {counter}</div>
})}
</div>;
Note
For such a trivial example, renderFrontend
is not really necessary. You could just directly render
the counter like this:
import { renderFrontend } from "uix/base/render-methods.ts";
const counter = $(0);
export default
<div>
<button onclick={()=>counter.val++}>Increment Counter</button>
<div>Counter : {counter}</div>
</div>;
renderFrontend
is useful for more complex scenarios, where you want to offload some rendering to the browser client, or where hybrid rendering is not working as expected.
As an alternative to renderFrontend
, you can also use frontend slots to mix backend- and frontend rendered content.
Frontend slots allow you to render parts of your content entirely on the frontend within predefined slots.
To do this, define your hybrid-rendered elements in the backend entrypoint as usual.
For the parts that should be rendered on the frontend, add a <frontend-slot>
element.
A <frontend-slot>
can contain placeholder content will be displayed until the actual slot content is fully rendered on the frontend.
Frontend slots can only be defined in the backend entrypoint.
export default (
<div>
<title>Hello</title>
<frontend-slot>
Loading...
</frontend-slot>
</div>
);
In the frontend entrypoint, define the content that that should be rendered inside the frontend slot.
export default (
<div>Created on frontend</div>
);
You can also assign frontend-rendered elements to specific slots with names:
export default (
<div>
<frontend-slot name="a"/>
More backend content
<frontend-slot name="b"/>
</div>
);
export default <>
<div slot="a">Div A</div>
<div slot="b">Div B</div>
</>;
As the name suggests, static rendering can be used for completely static pages that provide no JavaScript-based interactivity (forms and form action methods are still supported).
Example:
// file: backend/entrypoint.tsx
import {renderStatic} from "uix/base/render-methods.ts";
import type {Context} from "uix/routing/context.ts";
function handleForm(ctx: Context) {
// handle form submit
}
export default renderStatic(
<form action={handleForm}>
My Static Form
<input type="text" name="name" placeholder="name"/>
<input type="submit" value="Submit form data"/>
</form>
);
When using backend rendering, there is no UIX or DATEX library loaded on the client side. All reactivity is done on the backend.
You can still execute JavaScript on the frontend using @frontend
decorators or :frontend
labels, but keep in mind that only default browser APIs are available.
Example:
import {renderBackend} from "uix/base/render-methods.ts";
const counter = $(0);
export default renderBackend(
<div>
Counter: {counter}
<button onclick={() => counter.val++}>Increment counter</button>
<button onclick:frontend={() => alert("Hello")}>Show alert</button>
</div>
);
Note
Event handlers assigned to a JSX element (such as onclick
handler on the increment button in the example above) are always executed in their original context by default.
This means that when the HTML element is created on the backend, the event handler is also called on the backend by default.
You can read more about this behavior in the chapter Functions and Contexts
Warning
Reactivity with backend rendering is still experimental and not yet fully supported. When using backend rendering, it is not guaranteed that all DOM changes will be reflected on the frontend.
Dynamic rendering behaves exactly the same as hybrid rendering, except that there is no HTML pre-rendering. The content is only visible after the UIX and DATEX libraries have been initialized on the frontend.
We recommend using this mode only if server-side rendering causes problems or is explicitly not desired.
Example:
import {renderDynamic} from "uix/base/render-methods.ts";
const counter = $(0);
export default renderDynamic(
<div>
Counter: {counter}
<button onclick={() => counter.val++}>Increment counter</button>
<button onclick:frontend={() => alert("Hello")}>Show alert</button>
</div>
);