JavaScript location in ASP.NET Core Blazor apps

Note

This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.

Warning

This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 9 version of this article.

Important

This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

For the current release, see the .NET 9 version of this article.

Load JavaScript (JS) code using any of the following approaches:

Warning

Only place a <script> tag in a component file (.razor) if the component is guaranteed to adopt static server-side rendering (static SSR) because the <script> tag can't be updated dynamically.

Warning

Don't place a <script> tag in a component file (.razor) because the <script> tag can't be updated dynamically.

Note

Documentation examples usually place scripts in a <script> tag or load global scripts from external files. These approaches pollute the client with global functions. For production apps, we recommend placing JS into separate JS modules that can be imported when needed. For more information, see the JavaScript isolation in JavaScript modules section.

Note

Documentation examples place scripts into a <script> tag or load global scripts from external files. These approaches pollute the client with global functions. Placing JS into separate JS modules that can be imported when needed is not supported in Blazor earlier than ASP.NET Core 5.0. If the app requires the use of JS modules for JS isolation, we recommend using ASP.NET Core 5.0 or later to build the app. For more information, use the Version dropdown list to select a 5.0 or later version of this article and see the JavaScript isolation in JavaScript modules section.

Load a script in <head> markup

The approach in this section isn't generally recommended.

Place the JavaScript (JS) tags (<script>...</script>) in the <head> element markup:

<head>
    ...

    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</head>

Loading JS from the <head> isn't the best approach for the following reasons:

  • JS interop may fail if the script depends on Blazor. We recommend loading scripts using one of the other approaches, not via the <head> markup.
  • The page may become interactive slower due to the time it takes to parse the JS in the script.

Load a script in <body> markup

Place the JavaScript tags (<script>...</script>) inside the closing </body> element after the Blazor script reference:

<body>
    ...

    <script src="{BLAZOR SCRIPT}"></script>
    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</body>

In the preceding example, the {BLAZOR SCRIPT} placeholder is the Blazor script path and file name. For the location of the script, see ASP.NET Core Blazor project structure.

Load a script from an external JavaScript file (.js) collocated with a component

Collocation of JavaScript (JS) files for Razor components is a convenient way to organize scripts in an app.

Razor components of Blazor apps collocate JS files using the .razor.js extension and are publicly addressable using the path to the file in the project:

{PATH}/{COMPONENT}.razor.js

  • The {PATH} placeholder is the path to the component.
  • The {COMPONENT} placeholder is the component.

When the app is published, the framework automatically moves the script to the web root. Scripts are moved to bin/Release/{TARGET FRAMEWORK MONIKER}/publish/wwwroot/{PATH}/{COMPONENT}.razor.js, where the placeholders are:

No change is required to the script's relative URL, as Blazor takes care of placing the JS file in published static assets for you.

This section and the following examples are primarily focused on explaining JS file collocation. The first example demonstrates a collocated JS file with an ordinary JS function. The second example demonstrates the use of a module to load a function, which is the recommended approach for most production apps. Calling JS from .NET is fully covered in Call JavaScript functions from .NET methods in ASP.NET Core Blazor, where there are further explanations of the Blazor JS API with additional examples. Component disposal, which is present in the second example, is covered in ASP.NET Core Razor component lifecycle.

The following JsCollocation1 component loads a script via a HeadContent component and calls a JS function with IJSRuntime.InvokeAsync. The {PATH} placeholder is the path to the component.

Important

If you use the following code for a demonstration in a test app, change the {PATH} placeholder to the path of the component (example: Components/Pages in .NET 8 or later or Pages in .NET 7 or earlier). In a Blazor Web App (.NET 8 or later), the component requires an interactive render mode applied either globally to the app or to the component definition.

Add the following script after the Blazor script (location of the Blazor start script):

<script src="{PATH}/JsCollocation1.razor.js"></script>

JsCollocation1 component ({PATH}/JsCollocation1.razor):

@page "/js-collocation-1"
@inject IJSRuntime JS

<PageTitle>JS Collocation 1</PageTitle>

<h1>JS Collocation Example 1</h1>

<button @onclick="ShowPrompt">Call showPrompt1</button>

@if (!string.IsNullOrEmpty(result))
{
    <p>
        Hello @result!
    </p>
}

@code {
    private string? result;

    public async void ShowPrompt()
    {
        result = await JS.InvokeAsync<string>(
            "showPrompt1", "What's your name?");
        StateHasChanged();
    }
}

The collocated JS file is placed next to the JsCollocation1 component file with the file name JsCollocation1.razor.js. In the JsCollocation1 component, the script is referenced at the path of the collocated file. In the following example, the showPrompt1 function accepts the user's name from a Window prompt() and returns it to the JsCollocation1 component for display.

{PATH}/JsCollocation1.razor.js:

function showPrompt1(message) {
  return prompt(message, 'Type your name here');
}

The preceding approach isn't recommended for general use in production apps because the approach pollutes the client with global functions. A better approach for production apps is to use JS modules. The same general principles apply to loading a JS module from a collocated JS file, as the next example demonstrates.

The following JsCollocation2 component's OnAfterRenderAsync method loads a JS module into module, which is an IJSObjectReference of the component class. module is used to call the showPrompt2 function. The {PATH} placeholder is the path to the component.

Important

If you use the following code for a demonstration in a test app, change the {PATH} placeholder to the path of the component. In a Blazor Web App (.NET 8 or later), the component requires an interactive render mode applied either globally to the app or to the component definition.

JsCollocation2 component ({PATH}/JsCollocation2.razor):

@page "/js-collocation-2"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>JS Collocation 2</PageTitle>

<h1>JS Collocation Example 2</h1>

<button @onclick="ShowPrompt">Call showPrompt2</button>

@if (!string.IsNullOrEmpty(result))
{
    <p>
        Hello @result!
    </p>
}

@code {
    private IJSObjectReference? module;
    private string? result;

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            /*
                Change the {PATH} placeholder in the next line to the path of
                the collocated JS file in the app. Examples:

                ./Components/Pages/JsCollocation2.razor.js (.NET 8 or later)
                ./Pages/JsCollocation2.razor.js (.NET 7 or earlier)
            */
            module = await JS.InvokeAsync<IJSObjectReference>("import",
                "./{PATH}/JsCollocation2.razor.js");
        }
    }

    public async void ShowPrompt()
    {
        if (module is not null)
        {
            result = await module.InvokeAsync<string>(
                "showPrompt2", "What's your name?");
            StateHasChanged();
        }
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            try
            {
                await module.DisposeAsync();
            }
            catch (JSDisconnectedException)
            {
            }
        }
    }
}

In the preceding example, JSDisconnectedException is trapped during module disposal in case Blazor's SignalR circuit is lost. If the preceding code is used in a Blazor WebAssembly app, there's no SignalR connection to lose, so you can remove the try-catch block and leave the line that disposes the module (await module.DisposeAsync();). For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

{PATH}/JsCollocation2.razor.js:

export function showPrompt2(message) {
  return prompt(message, 'Type your name here');
}

Use of scripts and modules for collocated JS in a Razor class library (RCL) is only supported for Blazor's JS interop mechanism based on the IJSRuntime interface. If you're implementing JavaScript [JSImport]/[JSExport] interop, see JavaScript JSImport/JSExport interop with ASP.NET Core Blazor.

For scripts or modules provided by a Razor class library (RCL) using IJSRuntime-based JS interop, the following path is used:

./_content/{PACKAGE ID}/{PATH}/{COMPONENT}.{EXTENSION}.js

  • The path segment for the current directory (./) is required in order to create the correct static asset path to the JS file.
  • The {PACKAGE ID} placeholder is the RCL's package identifier (or library name for a class library referenced by the app).
  • The {PATH} placeholder is the path to the component. If a Razor component is located at the root of the RCL, the path segment isn't included.
  • The {COMPONENT} placeholder is the component name.
  • The {EXTENSION} placeholder matches the extension of component, either razor or cshtml.

In the following Blazor app example:

  • The RCL's package identifier is AppJS.
  • A module's scripts are loaded for the JsCollocation3 component (JsCollocation3.razor).
  • The JsCollocation3 component is in the Components/Pages folder of the RCL.
module = await JS.InvokeAsync<IJSObjectReference>("import", 
    "./_content/AppJS/Components/Pages/JsCollocation3.razor.js");

For more information on RCLs, see Consume ASP.NET Core Razor components from a Razor class library (RCL).

Load a script from an external JavaScript file (.js)

Place the JavaScript (JS) tags (<script>...</script>) with a script source (src) path inside the closing </body> element after the Blazor script reference:

<body>
    ...

    <script src="{BLAZOR SCRIPT}"></script>
    <script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

For the placeholders in the preceding example:

  • The {BLAZOR SCRIPT} placeholder is the Blazor script path and file name. For the location of the script, see ASP.NET Core Blazor project structure.
  • The {SCRIPT PATH AND FILE NAME (.js)} placeholder is the path and script file name under wwwroot.

In the following example of the preceding <script> tag, the scripts.js file is in the wwwroot/js folder of the app:

<script src="js/scripts.js"></script>

You can also serve scripts directly from the wwwroot folder if you prefer not to keep all of your scripts in a separate folder under wwwroot:

<script src="scripts.js"></script>

When the external JS file is supplied by a Razor class library, specify the JS file using its stable static web asset path: _content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}:

  • The {PACKAGE ID} placeholder is the library's package ID. The package ID defaults to the project's assembly name if <PackageId> isn't specified in the project file.
  • The {SCRIPT PATH AND FILE NAME (.js)} placeholder is the path and file name under wwwroot.
<body>
    ...

    <script src="{BLAZOR SCRIPT}"></script>
    <script src="_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

In the following example of the preceding <script> tag:

  • The Razor class library has an assembly name of ComponentLibrary, and a <PackageId> isn't specified in the library's project file.
  • The scripts.js file is in the class library's wwwroot folder.
<script src="_content/ComponentLibrary/scripts.js"></script>

For more information, see Consume ASP.NET Core Razor components from a Razor class library (RCL).

Inject a script before or after Blazor starts

To ensure scripts load before or after Blazor starts, use a JavaScript initializer. For more information and examples, see ASP.NET Core Blazor startup.

Inject a script after Blazor starts

To inject a script after Blazor starts, chain to the Promise that results from a manual start of Blazor. For more information and an example, see ASP.NET Core Blazor startup.

JavaScript isolation in JavaScript modules

Blazor enables JavaScript (JS) isolation in standard JS modules (ECMAScript specification).

JS isolation provides the following benefits:

  • Imported JS no longer pollutes the global namespace.
  • Consumers of a library and components aren't required to import the related JS.

In server-side scenarios, always trap JSDisconnectedException in case loss of Blazor's SignalR circuit prevents a JS interop call from disposing a module, which results in an unhandled exception. Blazor WebAssembly apps don't use a SignalR connection during JS interop, so there's no need to trap JSDisconnectedException in Blazor WebAssembly apps for module disposal.

For more information, see the following resources: