Blazor  

How to Call JavaScript from Blazor Without Performance Issues

Introduction

Blazor allows developers to build modern web applications using C# instead of JavaScript. However, there are still many scenarios where you need to interact with JavaScript, such as using browser APIs, third-party libraries, charts, maps, or DOM manipulation.

This is where JavaScript Interop (JS Interop) comes into play.

JS Interop in Blazor enables communication between C# and JavaScript. You can call JavaScript functions from C#, and you can also call C# methods from JavaScript.

In this article, we will understand JS Interop in simple words, learn how to use it step by step, and most importantly, explore best practices to avoid performance issues in real-world Blazor applications.

What is JavaScript Interop in Blazor?

JavaScript Interop is a feature in Blazor that allows seamless communication between C# code and JavaScript code.

There are two main directions:

  • Calling JavaScript from C#

  • Calling C# from JavaScript

This helps when:

  • You need browser APIs like localStorage, geolocation, or clipboard

  • You want to use existing JavaScript libraries

  • You need fine control over DOM elements

Calling JavaScript from Blazor (C# to JS)

To call JavaScript from Blazor, you use the IJSRuntime interface.

Step 1: Add JavaScript Function

Create a JavaScript file (for example: wwwroot/js/site.js):

function showAlert(message) {
    alert(message);
}

Include it in your HTML:

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

Step 2: Inject IJSRuntime in Blazor

@inject IJSRuntime JS

Step 3: Call JavaScript Function

await JS.InvokeVoidAsync("showAlert", "Hello from Blazor!");

Explanation in simple words:

  • InvokeVoidAsync is used when no value is returned

  • You pass the function name and parameters

Calling JavaScript with Return Value

If your JavaScript function returns a value:

function getBrowserWidth() {
    return window.innerWidth;
}

Call from C#:

int width = await JS.InvokeAsync<int>("getBrowserWidth");

This allows you to use JavaScript results inside your Blazor logic.

Calling C# from JavaScript (JS to .NET)

Blazor also allows JavaScript to call C# methods.

Step 1: Create C# Method

[JSInvokable]
public static string SayHello()
{
    return "Hello from .NET";
}

Step 2: Call from JavaScript

DotNet.invokeMethodAsync('YourAssemblyName', 'SayHello')
    .then(result => console.log(result));

This is useful when integrating JavaScript-heavy libraries.

Using JS Modules (Recommended Approach)

Instead of global JS functions, use JavaScript modules for better performance and maintainability.

Step 1: Create Module File

export function showToast(message) {
    console.log(message);
}

Step 2: Import in Blazor

IJSObjectReference module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/site.js");
await module.InvokeVoidAsync("showToast", "Hello Module");

Why this is better:

  • Avoids global scope pollution

  • Improves performance

  • Cleaner code organization

Avoiding Performance Issues in JS Interop

JS Interop is powerful, but misuse can cause performance problems. Let’s understand how to avoid them.

1. Minimize Frequent Calls

Each JS interop call has overhead.

Bad practice:

for (int i = 0; i < 1000; i++)
{
    await JS.InvokeVoidAsync("log", i);
}

Better approach:

  • Batch data and send once

await JS.InvokeVoidAsync("logBatch", dataList);

2. Use Async Calls Properly

Always use async methods like InvokeAsync.

Why:

  • Prevents UI blocking

  • Improves responsiveness

3. Prefer JS Modules Over Global Functions

Modules are loaded once and reused.

Benefit:

  • Reduces repeated loading

  • Improves performance in large applications

4. Avoid Large Data Transfers

Passing large objects between C# and JS is expensive.

Tip:

  • Send only required data

  • Use serialization carefully

5. Cache JS References

Instead of importing modules repeatedly, cache them.

Example:

private IJSObjectReference module;

protected override async Task OnInitializedAsync()
{
    module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/site.js");
}

6. Dispose JS Resources Properly

Always dispose modules when not needed.

await module.DisposeAsync();

This prevents memory leaks.

7. Use IJSInProcessRuntime (Blazor WebAssembly Only)

For synchronous calls in WebAssembly:

var jsInProcess = (IJSInProcessRuntime)JS;
jsInProcess.InvokeVoid("fastCall");

Note:

  • Only use when necessary

  • Not available in Blazor Server

Real-World Use Cases

JS Interop is commonly used for:

  • Charts (Chart.js, D3.js)

  • Maps (Google Maps, Leaflet)

  • Browser storage (localStorage, sessionStorage)

  • File downloads and uploads

  • DOM manipulation

Example:

await JS.InvokeVoidAsync("localStorage.setItem", "key", "value");

Common Mistakes to Avoid

  • Calling JS too frequently

  • Not handling async properly

  • Forgetting to dispose modules

  • Sending large data unnecessarily

  • Using global JS functions everywhere

Summary

JavaScript Interop in Blazor is essential for building real-world web applications that need browser APIs and external libraries. By using IJSRuntime, JS modules, and proper async patterns, developers can efficiently call JavaScript from C# and vice versa. To avoid performance issues, it is important to minimize calls, batch data, cache modules, and manage resources properly. With the right approach, JS Interop becomes a powerful tool for building scalable, high-performance Blazor applications in modern .NET development.