Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs for "Static server-side rendering guidance for Blazor component library owners" #31066

Closed
SteveSandersonMS opened this issue Nov 17, 2023 · 3 comments · Fixed by #31090
Closed
Assignees
Labels
8.0 .NET 8 Blazor doc-idea Pri1 High priority, do before Pri2 and Pri3

Comments

@SteveSandersonMS
Copy link
Member

This is to implement dotnet/aspnetcore#47624

I'm proposing the following content below. @guardrex, is this something you could copy-edit and do whatever refinements you think are needed, and add to the 8.0 docs?


Component Libraries and Static Server-side Rendering (SSR)

Blazor encourages an ecosystem of open-source and commercial component libraries. Developers might also create reusable components to be shared privately across multiple applications within their own companies. Ideally, these components will be compatible with as many hosting models and rendering modes as possible. Static server-side rendering introduces additional restrictions and so can be more challenging to support than interactive rendering modes based on Blazor Server or Blazor WebAssembly.

This document provides guidance for component authors considering whether and how to support Static Server-side Rendering (SSR).

Understand the capabilities and restrictions of Static Server-side Rendering

Static server-side rendering is a mode in which components run when the server handles an incoming HTTP request. Blazor renders the component as HTML, which is included in the response. Once the response is sent, the server-side component and renderer state is discarded, so all that remains is HTML in the browser.

The benefit of this mode is cheaper, more scalable hosting, because no ongoing server resources are needed to hold component state, no ongoing connection must be maintained between browser and server, and no WebAssembly payload is required in the browser.

The cost of this mode is that event handlers such as @onclick cannot be run, because there is no .NET code in the browser to run them, and the server has immediately discarded any component and renderer state that would be needed to execute event handlers or to re-render the same component instances.

How interactive components behave by default under Static Server-side Rendering

There are many existing Blazor components built with the assumption that events such as @onclick will execute handler logic. By default, all these existing components can still be used with static server-side rendering, however they will only emit their initial HTML, and their event handlers will not fire (with the special exception of @onsubmit - see below). This is equivalent to what those components have always done when being prerendered, before any Blazor Server circuit or Blazor WebAssembly runtime was started.

For components whose only role is to produce readonly DOM content, this is already completely sufficient. But for components whose role is to have interactive behaviors, the author must consider what to do.

Options for component authors

There are three main choices available:

  1. (Simple) Don't use any interactive behaviors

    For components whose only role is to produce read-only DOM content, the developer need not take any special action. These components will naturally work with any render mode. Examples:

    • A "user card" component that loads data corresponding to a person and renders it in a stylised way with a photo, job title, and so on.
    • A "video" component that acts as a wrapper around the HTML <video> element, making it more convenient to use from Blazor.
  2. (Simple) Require interactive rendering

    You can choose to require that your component is only used with interactive rendering. This limits the applicability of your component, but means that you may freely rely on arbitrary event handlers. Even then, you should still avoid declaring a specific @rendermode, so the application author is free to select one. Examples:

    • A video editing component in which users may splice and reorder segments of video. Even if there was a way to represent these edit operations with plain HTML buttons and form posts, the user experience would be unacceptable without true interactivity.
    • A collaborative document editor that must show the activities of other users in real-time.
  3. (Advanced) Use interactive behaviors, but design for static SSR and progressive enhancement

    Many interactive behaviors can implemented using only HTML capabilities. With a good understanding of HTML and CSS, you can often produce a useful baseline of functionality that will work with static SSR. You can still declare event handlers that implement more advanced, optional behaviors, which will only work in interactive render modes. Examples:

    • A grid component. Under static SSR, it may only support displaying data and navigating between pages (implemented with <a> links), but when used with interactive rendering may add live sorting and filtering.
    • A tabset component. As long as navigation between tabs is achieved using <a> links and state is held only in URL query parameters, this can work without needing @onclick.
    • An advanced file upload component. Under static SSR it may behave as a native <input type=file>, but when used with interactive rendering it could also display upload progress.
    • A stock ticker. Under static SSR it may display the stock quote at the time the HTML was rendered, but when used with interactive rendering may then update in real-time.

In any of these strategies, there's also the option of implementing some interactive features using JavaScript.

To choose among all these options, reusable Razor component authors must make a cost/benefit tradeoff. Your component will be more useful and have a broader potential user base if it supports all render modes including static SSR, but it takes more work to design and implement a component that supports and takes best advantage of each render mode.

When to use the @rendermode directive

In almost all cases, reusable component authors should not specify a render mode at all, even when interactivity is required. This is because the component author does not know whether the application will enable support for InteractiveServer, InteractiveWebAssembly, or both. By not specifying any @rendermode, the component author leaves the choice to the application developer.

Even if the component author thinks that interactivity is required, there may still be cases where an application author considers it sufficient to use static SSR alone. For example, a draggable, zoomable map component may seem to require interactivity, but in some scenario an application author may consider it useful just to render a static map image and does not need the drag/zoom behaviors.

The only reason why a reusable component author should use the @rendermode directive on their component is if the implementation is fundamentally coupled to one specific render mode and would certainly cause an error if used in a different mode. For example, if a component's core purpose is to interact directly with the host OS using Windows or Linux-specific APIs - in that case it may be impossible to use on WebAssembly, and so it may be reasonable to declare @rendermode InteractiveServer.

Using streaming rendering

Reusable Razor components are free to declare @attribute [StreamRendering]. This will result in incremental UI updates during static SSR. Since the same data loading patterns produce incremental UI updates during interactive render modes (regardless of the StreamRendering attribute), the component can behave correctly in all cases. Even in cases where streaming SSR is suppressed on the server, the component will still render its correct final state.

Using links across all render modes

Reusable Razor components may use links and enhanced navigation. HTML <a> tags should produce equivalent behaviors with or without an interactive <Router> component, and whether or not enhanced navigation is enabled/disabled at an ancestor level in the DOM.

Using forms across all render modes

Reusable Razor components may include forms (either <form @onsubmit=...> or <EditForm OnValidSubmit=...>), as these can be implemented to work equivalently across both static and interactive render modes. Example:

<EditForm Enhance FormName="create-product" method="post" Model="@Item" OnValidSubmit="SaveProductAsync">
    <p>Name: <InputText @bind-Value="@Item.Name" /></p>

    <button type="submit">Submit</button>

    <DataAnnotationsValidator />
    <ValidationSummary />
</EditForm>

@code {
    [SupplyParameterFromForm] public Product Item { get; set; } = new();

    private async Task SaveProductAsync()
    {
        // ...
    }
}

In this example, the Enhance, FormName, method="post" and SupplyParameterFromForm APIs are only used during static SSR, and are ignored during interactive rendering. The form will work during both interactive and static rendering.

Avoiding APIs that are specific to static SSR

To make a reusable component that works across all render modes, do not rely on HttpContext, because it is only available during static SSR. The HttpContext API would not make sense to use during Server or WebAssembly rendering, because at those times there is no active HTTP request in flight, so it is meaningless to think about setting a status code or writing to the response.

Reusable components are free to receive an HttpContext when available, as follows:

[CascadingParameter] public HttpContext? Context { get; set; }

The value will be null during interactive rendering, and will only be set during static SSR.

In many cases, there are better alternatives than using HttpContext. For example, if you need to know the current URL, or to perform a redirection, use the APIs on NavigationManager since they work on all render modes, both static and interactive. Likewise, if you need to know the user's authentication status, then instead of using HttpContext.User, prefer to use Blazor's AuthenticationStateProvider service.

@guardrex
Copy link
Collaborator

Sure thing ... I'll create a docs issue and get to it ...... mmmmm 🤔 ... Tuesday .... I think.

@SteveSandersonMS
Copy link
Member Author

@guardrex Cool, no particular rush here, just adding it to the pipeline.

@exuereb
Copy link

exuereb commented Nov 22, 2023

I have been having a play with SSR, trying to convert an existing server size blazor components to static ones. One work around that I found was to replace @OnClick events with EditForm form submission and client javascript. For example, an anchor tag with an @OnClick event can is replaced with client javascript which updates a hidden field and submits the form. The server can then process the form fields and return an updated model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
8.0 .NET 8 Blazor doc-idea Pri1 High priority, do before Pri2 and Pri3
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

4 participants