Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions aspnetcore/blazor/components/render-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ In the following example, there's no designation for the component's render mode

If using the preceding component locally in a Blazor Web App, place the component in the server project's `Components/Pages` folder. The server project is the solution's project with a name that doesn't end in `.Client`. When the app is running, navigate to `/render-mode-1` in the browser's address bar.

[Enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling) with static rendering requires special attention when loading JavaScript. For more information, see <xref:blazor/js-interop/ssr>.

## Server render mode

The Server render mode renders the component interactively from the server using Blazor Server. User interactions are handled over a real-time connection with the browser. The circuit connection is established when the Server component is rendered.
Expand Down Expand Up @@ -586,3 +588,7 @@ Use the shorthand render mode in components:
> For more information, see the [Prerendering](#prerendering) section.

At the moment, the shorthand render mode approach is probably only useful for reducing the verbosity of specifying the `prerender` flag. The shorthand approach might be more useful in the future if additional flags become available for interactive rendering and you would like to create shorthand render modes with different combinations of flags.

## Additional resources

* <xref:blazor/js-interop/ssr>
2 changes: 2 additions & 0 deletions aspnetcore/blazor/fundamentals/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,8 @@ Blazor.addEventListener('enhancedload', () => console.log('Enhanced update!'));

To disable enhanced navigation and form handling globally, see <xref:blazor/fundamentals/startup#disable-enhanced-navigation-and-form-handling>.

Enhanced navigation with [static server rendering](xref:blazor/components/render-modes) requires special attention when loading JavaScript. For more information, see <xref:blazor/js-interop/ssr>.

:::moniker-end

## Produce a URI relative to the base URI prefix
Expand Down
1 change: 1 addition & 0 deletions aspnetcore/blazor/fundamentals/startup.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ For an examples of JS initializers, see the following resources:

:::moniker range=">= aspnetcore-8.0"

* <xref:blazor/js-interop/ssr>
* <xref:blazor/components/js-spa-frameworks#render-razor-components-from-javascript> (*`quoteContainer2` example*)
* <xref:blazor/components/event-handling#custom-event-arguments> (*Custom clipboard paste event example*)
* [Basic Test App in the ASP.NET Core GitHub repository (`BasicTestApp.lib.module.js`)](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/wwwroot/BasicTestApp.lib.module.js)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
title: ASP.NET Core Blazor JavaScript with Blazor static server rendering
author: guardrex
description: Learn how to use JavaScript in a Blazor Web App with static server rendering.
monikerRange: '>= aspnetcore-8.0'
ms.author: riande
ms.custom: mvc
ms.date: 11/03/2023
uid: blazor/js-interop/ssr
---
# ASP.NET Core Blazor JavaScript with Blazor static server rendering

<!-- UPDATE 9.0 Activate after release and INCLUDE is updated

[!INCLUDE[](~/includes/not-latest-version.md)]

-->

This article explains how to load JavaScript (JS) in a Blazor Web App with static server rendering and [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling).

Some applications may depend on JS to perform initialization tasks that are specific to each page. When using Blazor's enhanced navigation feature, which allows the user to avoid reloading the entire page, JS code may not re-execute as expected. Therefore, page-specific `<script>` elements may not be executed again as expected each time an enhanced page navigation occurs.

To avoid this problem, we don't recommended relying on page-specific `<script>` elements outside of the app's common layout. Instead, scripts should register an [`afterWebStarted` JS initializer](xref:blazor/fundamentals/startup#javascript-initializers) to perform initialization logic and use an event listener (`blazor.addEventListener("enhancedload", callback)`) to listen for page updates caused by enhanced navigation.

The following example demonstrates one way to configure JS code to run when a page is initially loaded or updated.

In the Blazor Web App, add the following `PageWithScript` component.

`Components/Pages/PageWithScript.razor`:

```razor
@page "/page-with-script"
@using BlazorPageScript

<PageTitle>Enhanced Load Script Example</PageTitle>

<PageScript Src="./Components/Pages/PageWithScript.razor.js" />

Welcome to my page.
```

In the Blazor Web App, add the following [collocated JS file](xref:blazor/js-interop/index#load-a-script-from-an-external-javascript-file-js-collocated-with-a-component) (`PageWithScript.razor.js`):

* `onLoad` is called when the script is added to the page.
* `onUpdate` is called when the script still exists on the page after an enhanced update.
* `onDispose` is called when the script is removed from the page after an enhanced update.

`Components/Pages/PageWithScript.razor.js`:

```javascript
export function onLoad() {
console.log('Loaded');
}

export function onUpdate() {
console.log('Updated');
}

export function onDispose() {
console.log('Disposed');
}
```

In a [Razor Class Library (RCL)](xref:blazor/components/class-libraries) (the example RCL is named `BlazorPageScript`), add the following module.

`wwwroot/BlazorPageScript.lib.module.js`:

```javascript
const pageScriptInfoBySrc = new Map();

function registerPageScriptElement(src) {
if (!src) {
throw new Error('Must provide a non-empty value for the "src" attribute.');
}

let pageScriptInfo = pageScriptInfoBySrc.get(src);

if (pageScriptInfo) {
pageScriptInfo.referenceCount++;
} else {
pageScriptInfo = { referenceCount: 1, module: null };
pageScriptInfoBySrc.set(src, pageScriptInfo);
initializePageScriptModule(src, pageScriptInfo);
}
}

function unregisterPageScriptElement(src) {
if (!src) {
return;
}

const pageScriptInfo = pageScriptInfoBySrc.get(src);
if (!pageScriptInfo) {
return;
}

pageScriptInfo.referenceCount--;
}

async function initializePageScriptModule(src, pageScriptInfo) {
if (src.startsWith("./")) {
src = new URL(src.substr(2), document.baseURI).toString();
}

const module = await import(src);

if (pageScriptInfo.referenceCount <= 0) {
return;
}

pageScriptInfo.module = module;
module.onLoad?.();
module.onUpdate?.();
}

function onEnhancedLoad() {
for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) {
if (referenceCount <= 0) {
module?.onDispose?.();
pageScriptInfoBySrc.delete(src);
}
}

for (const { module } of pageScriptInfoBySrc.values()) {
module?.onUpdate?.();
}
}

export function afterWebStarted(blazor) {
customElements.define('page-script', class extends HTMLElement {
static observedAttributes = ['src'];

attributeChangedCallback(name, oldValue, newValue) {
if (name !== 'src') {
return;
}

this.src = newValue;
unregisterPageScriptElement(oldValue);
registerPageScriptElement(newValue);
}

disconnectedCallback() {
unregisterPageScriptElement(this.src);
}
});

blazor.addEventListener('enhancedload', onEnhancedLoad);
}
```

In the RCL, add the following `PageScript` component.

`PageScript.razor`:

```razor
<page-script src="@Src"></page-script>

@code {
[Parameter]
[EditorRequired]
public string Src { get; set; } = default!;
}
```

The `PageScript` component functions normally on the top-level of a page.

If you place the `PageScript` component in the app's layout (for example, `Components/Layout/MainLayout.razor`), which results in a shared `PageScript` among pages that use the layout, then the component only runs `onLoad` after a full page reload and `onUpdate` when any enhanced page update occurs, including enhanced navigation.

To reuse the same module among pages, but have the `onLoad` and `onDispose` callbacks invoked on each page change, append a query string to the end of the script so that it's recognized as a different module. An app could adopt the convention of using the component's name as the query string value. In the following example, the query string is "`counter`" because this `PageScript` component reference is placed in a `Counter` component. This is merely a suggestion, and you can use whatever query string scheme that you prefer.

```razor
<PageScript Src="./Components/Pages/PageWithScript.razor.js?counter" />
```

To monitor changes in specific DOM elements, use the [`MutationObserver`](https://developer.mozilla.org/docs/Web/API/MutationObserver) pattern in JS on the client. For more information, see <xref:blazor/js-interop/index#dom-cleanup-tasks-during-component-disposal>.
2 changes: 2 additions & 0 deletions aspnetcore/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,8 @@ items:
uid: blazor/js-interop/call-dotnet-from-javascript
- name: JSImport/JSExport interop
uid: blazor/js-interop/import-export-interop
- name: Static server rendering
uid: blazor/js-interop/ssr
- name: Call a web API
uid: blazor/call-web-api
- name: Images
Expand Down