Skip to content

Server rendering

wybthon.server

server

Render components to HTML on the server, for hydration in the browser.

Wybthon components are plain Python, so the same code that runs in the browser under Pyodide renders to HTML in CPython. Serve that HTML with the page, and the browser shows content immediately instead of waiting for Pyodide to boot. Once it has, hydrate adopts the server's DOM and makes it interactive.

Three entry points cover the common deployment shapes:

  • render_to_string renders synchronously. Async data isn't loaded; its Loading boundaries render their fallbacks and the browser loads it after hydration.
  • render_to_string_async waits for every async memo the page reads (including memos that only appear once other data arrives) and embeds the results, so the browser neither fetches them again nor shows a loading state.
  • render_to_stream sends the page with fallbacks immediately, then streams each boundary's content as its data arrives.

All three return (or yield) the contents of the mount container; the caller writes the surrounding document. Output always ends with a <script type="application/json" data-wyb-state> element, which hydrate reads and removes.

Example

With any ASGI framework:

from wybthon.server import render_to_string_async

async def page(request):
    body = await render_to_string_async(App(), url=str(request.url.path))
    return HTMLResponse(TEMPLATE.replace("<!-- app -->", body))

Rendering runs the ordinary renderer against an in-memory DOM, so server output always matches what the browser's hydration expects. Renders may run concurrently on one event loop; don't render from several threads at once.

See Also

Functions:

Name Description
render_to_string

Render view to HTML synchronously.

render_to_string_async

Render view to HTML once every async memo it reads has resolved.

render_to_stream

Stream view as HTML: the page first, then each Loading boundary as it resolves.

render_to_string

render_to_string(view: Any, *, url: str = '/') -> str

Render view to HTML synchronously.

Async memos don't start: the Loading boundaries that read them render their fallbacks, and the browser loads the data after hydrate. Use render_to_string_async to include async data.

Parameters:

Name Type Description Default
view Any

The root view: a VNode (such as App()) or a zero-arg callable returning one.

required
url str

The request path and query, read by the router.

'/'

Returns:

Type Description
str

The HTML for the mount container's contents, ending with the

str

serialized state script.

render_to_string_async async

render_to_string_async(view: Any, *, url: str = '/', timeout: float = 30.0) -> str

Render view to HTML once every async memo it reads has resolved.

The tree renders in passes: each pass renders with the values resolved so far, then waits for the async memos it started. A later pass can start memos that only render once earlier data has arrived. The final pass renders synchronously from start to finish, exactly as the browser will while hydrating, and its values are serialized for hydrate.

Parameters:

Name Type Description Default
view Any

The root view: a VNode (such as App()) or a zero-arg callable returning one.

required
url str

The request path and query, read by the router.

'/'
timeout float

Seconds to wait for data in total. Whatever hasn't resolved by then renders its Loading fallback and loads in the browser.

30.0

Returns:

Type Description
str

The HTML for the mount container's contents, ending with the

str

serialized state script.

render_to_stream async

render_to_stream(view: Any, *, url: str = '/', timeout: float = 30.0) -> AsyncIterator[str]

Stream view as HTML: the page first, then each Loading boundary as it resolves.

The first chunk is the page with a fallback in every boundary whose data isn't ready. Each later chunk carries the content of the boundaries that became ready, as <template> elements plus a small inline script that swaps them into place (out-of-order streaming). The last chunk is the serialized state for hydrate. Write every chunk inside the mount container, in order.

Reveal ordering is respected: a boundary is sent once it would show its content in the browser.

Parameters:

Name Type Description Default
view Any

The root view: a VNode (such as App()) or a zero-arg callable returning one.

required
url str

The request path and query, read by the router.

'/'
timeout float

Seconds to wait for data in total. Boundaries still pending then keep their fallbacks and load in the browser.

30.0

Yields:

Type Description
AsyncIterator[str]

HTML chunks for the mount container.

What's in this module

Server rendering: the same components that run in the browser render to HTML in CPython. See Server rendering for the full guide.

Name Description
render_to_string Render synchronously. Async data isn't loaded; Loading boundaries render their fallbacks.
render_to_string_async Resolve every async memo the page reads, then render and embed the results.
render_to_stream Yield the page with fallbacks, then each Loading boundary as its data arrives, then the state.

The browser side, hydrate, is_server, client_only, and ServerError, are exported from wybthon.

import asyncio
from wybthon import component, p
from wybthon.server import render_to_string_async

@component
def App():
    return p("Hello from the server")

html = asyncio.run(render_to_string_async(App(), url="/"))

See also