DEV Community

Cover image for Integrating Next.js and SignalR to build real-time web apps
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

Integrating Next.js and SignalR to build real-time web apps

Written by Clara Ekekenta✏️

Imagine you're watching a live sports game online and you see the score update instantly on your screen as a team scores, without you needing to refresh the application. That's the power of real-time web applications — they allow you to send and receive data in real time.

In this tutorial, you'll learn how to integrate Next.js and SignalR to build an enhanced real-time web application. This tutorial assumes that you have Node.js, npm, and the .NET 8.0 SDK installed, along with some prior knowledge of Next.js and C#.

What is SignalR?

SignalR is an open source ASP.NET library designed to simplify the process of building real-time web applications. These real-time applications can send up-to-date information to connected users as soon as the data becomes available, instead of waiting for users to refresh the page.

Here are some reasons why developers use SignalR:

  • Allows you to easily build real-time web applications, such as chat apps and live dashboards, with little overhead
  • Uses the WebSocket protocol by default for real-time communications while hybridizing it with other available protocols such as HDXP, Forever Frames, or long polling when WebSocket is not available
  • Enables intercommunication by allowing the client to call the SignalR methods on the server side to trigger the push of data to the client
  • Real-time functionality works well in large apps or enterprise applications with no problems regarding scalability, even with growing numbers of users and connections
  • Abstracts the complexities involved in setting up real-time apps, such as communication management and data handling
  • A good choice for apps where frequent updates from the server are crucial, such as online gaming apps and collaborative apps like live document editors

There are also many other potential use cases for SignalR in various types of real-time apps.

Why use SignalR with Next.js?

SignalR and Next.js have unique features that are relevant to building real-time web applications. Let's explore why this combination is a good choice for developers:

  • Complementary strengths: SignalR, known to be the fastest and the most reliable real-time-communication library, sends data to clients in the fastest possible manner. Next.js, inheriting the features of React, offers a better way for creating responsive user interfaces. This integration facilitates the development of performant and user friendly real-time apps
  • Streamlined updates with Next.js: Next.js uses React's state management features, which makes it equally adept at connecting to SignalR's live data streams. This integration helps in updating the user interface to get it synchronized with the server in real-time, which makes sure that the recent data is always shown in the user interface without any perceptible lag
  • Scalability and performance: Both SignalR and Next.js are tools built to be scalable and performant. SignalR manages hundreds of WebSockets allotments, making it the best fit for apps with high traffic. This is also where Next.js comes in with its bundling and virtual DOM features. Due to the virtual DOM, the user interface remains responsive and fast, even with frequent data updates
  • Modular approach of Next.js: The component-based Next.js architecture aligns perfectly with SignalR's real-time data streams. Next.js can independently update its UI in response to new data updates from SignalR. Together, these features contribute to well-managed and scalable code
  • Flexible integration: SignalR is relatively flexible and thus can integrate very well with frontend frameworks like Next.js. This versatility is essential in modern web development, where decoupling frontend and backend functionalities is a common practice
  • Enhanced real-time features: SignalR closes the gap between synchronous applications working in real time by enabling the addition of messaging and live update features into web apps. Next.js complements SignalR features by making sure the UI changes in response to data or state changes
  • Community and documentation support: Thanks to the Microsoft and React communities, developers have tons of resources, documentation, and community support. This makes it easy to stay up-to-date with the version releases and troubleshooting any issues
  • Cross-platform reach: Combining SignalR and Next.js helps developers build web applications that are accessible across different browsers and that are also able to handle requests from a large user base

As you can see, using both frameworks together is an effective strategy for building performant, scalable, and SEO-rich real-time applications. If you’re interested in using a different framework, you can check out this tutorial on using SignalR with Angular.

Integrate Next.js and SignalR

For the demonstrations in this tutorial, we’ll build an ASP.NET Core signal server to send and receive messages from the Next.js client application. Then, we’ll build a Next.js frontend that integrates with the server to allow users to chat in real time.

You can find the source code for our demo project in this GitHub repository.

Creating the ASP.NET Server

To get started, create a new ASP.NET web API with the command below:

dotnet new webapi -n real-time-app
Enter fullscreen mode Exit fullscreen mode

The above command will scaffold a new ASP.NET API server with a demo API. Now, run the server with the command below:

dotnet run
Enter fullscreen mode Exit fullscreen mode

The application will be built and run on http://localhost:5248/: Terminal Showing App Building To Be Run On Localhost 5248 You can preview the demo API on your browser by visiting the /weatherforecast endpoint: Preview Of The Demo Api On Browser Next, update the code in the Program.cs file to add AddSignalR for real-time communication:

using SignalRApp.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Configure CORS
builder.Services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy", policy =>
    {
        policy.WithOrigins("http://localhost:3000")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials();
    });
});
// Add SignalR services
builder.Services.AddSignalR();
var app = builder.Build();
// Use CORS with the specified policy
app.UseCors("CorsPolicy");
// Use default files and static files
app.UseDefaultFiles();
app.UseStaticFiles();
// Map the MessagingHub to the "/hub" endpoint
app.MapHub<MessagingHub>("/hub");
// Run the application
app.Run();
Enter fullscreen mode Exit fullscreen mode

In the code snippet, we initialize a new builder instance and configure CORS policies, enabling our front end to interact with the ASP.NET server. We then register SignalR services to facilitate real-time communication and map requests to the /hub endpoint to the MessagingHub class.

MessagingHub is a SignalR hub that manages real-time WebSocket connections, essential for functionalities like chat.

Creating a SignalR hub class

Now, let's create a new folder called messageHub. In the messageHub directory, create a new file named MessageHub.cs and add the code snippet below:

using Microsoft.AspNetCore.SignalR;
namespace SignalRApp.Hubs
{
    public class UserMessage
    {
        public required string Sender { get; set; }
        public required string Content { get; set; }
        public DateTime SentTime { get; set; }
    }
    public class MessagingHub : Hub
    {
        private static readonly List<UserMessage> MessageHistory = new List<UserMessage>();
        public async Task PostMessage(string content)
        {
            var senderId = Context.ConnectionId;
            var userMessage = new UserMessage
            {
                Sender = senderId,
                Content = content,
                SentTime = DateTime.UtcNow
            };
            MessageHistory.Add(userMessage);
            await Clients.Others.SendAsync("ReceiveMessage", senderId, content, userMessage.SentTime);
        }
        public async Task RetrieveMessageHistory() => 
            await Clients.Caller.SendAsync("MessageHistory", MessageHistory);
    }
}
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we defined two WebSocket methods:

  • One to receive messages from the client side — in other words, our Next.js application
  • Another to send messages back to the client using SignalR WebSocket

For PostMessage, we are using SignalR's Others.SendAsync client method to send messages to all connected clients except the sender. Conversely, we used Caller.SendAsync to send the previous messages back to the sender.

Creating the Next.js frontend

Now let’s proceed to the frontend part of our application. Run the following command to scaffold a new Next.js application:

npx create-next-app@latest chat-app --typescript
Enter fullscreen mode Exit fullscreen mode

The command above will prompt you to choose configurations for your project. For this tutorial, your selections should match those shown in the screenshot below: Next Js Project Configurations Next, install the SignalR package with the command below:

npm install @microsoft/signalr
Enter fullscreen mode Exit fullscreen mode

Then create a chat folder in the app directory and add a page.tsx file. Add the code snippet below to the file:

"use client";
import { useEffect, useState } from "react";
import {
  HubConnection,
  HubConnectionBuilder,
  LogLevel,
} from "@microsoft/signalr";

type Message = {
  sender: string;
  content: string;
  sentTime: Date;
};
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we set up a HubConnection using HubConnectionBuilder from the @microsoft/signalr package and imported LogLevel to obtain logging information from the server. Additionally, we defined a Message type representing the structure of our message object, which includes sender, content, and sentTime fields.

Now, let’s create a Chat component in the file and define the state for our application. We'll use a messages state to store messages, a newMessage state to capture new messages from the input field, and a connection state to keep track of the SignalR connection.

We will accomplish this with the following code snippets:

//...
const Chat = () => {
  const [messages, setMessages] = useState<Message[]>([]);
  const [newMessage, setNewMessage] = useState("");
  const [connection, setConnection] = useState<HubConnection | null>(null);
  useEffect(() => {
    const connect = new HubConnectionBuilder()
      .withUrl("http://localhost:5237/hub")
      .withAutomaticReconnect()
      .configureLogging(LogLevel.Information)
      .build();
    setConnection(connect);
    connect
      .start()
      .then(() => {
        connect.on("ReceiveMessage", (sender, content, sentTime) => {
          setMessages((prev) => [...prev, { sender, content, sentTime }]);
        });
        connect.invoke("RetrieveMessageHistory");
      })

      .catch((err) =>
        console.error("Error while connecting to SignalR Hub:", err)
      );

    return () => {
      if (connection) {
        connection.off("ReceiveMessage");
      }
    };
  }, []);
  const sendMessage = async () => {
    if (connection && newMessage.trim()) {
      await connection.send("PostMessage", newMessage);
      setNewMessage("");
    }
  };
  const isMyMessage = (username: string) => {
    return connection && username === connection.connectionId;
  };
  return (
    //...
  )
}
export default Chat;
Enter fullscreen mode Exit fullscreen mode

In the above code snippet, we used the useEffect Hook to establish a connection between the server and our client side when the component mounts. This will update our component whenever a new message is sent or received from other connected clients.

Then, we defined the sendMessage method to send new messages to the server using the send method from the SignalR connection. Additionally, we implemented isMyMessage to distinguish our messages from those sent by other connected clients.

Next, update the return method to display the messages with code snippets:

//...
 return (
    <div className="p-4">
      <div className="mb-4">
        {messages.map((msg, index) => (
          <div
            key={index}
            className={`p-2 my-2 rounded ${
              isMyMessage(msg.sender) ? "bg-blue-200" : "bg-gray-200"
            }`}
          >
            <p>{msg.content}</p>
            <p className="text-xs">
              {new Date(msg.sentTime).toLocaleString()}
            </p>
          </div>
        ))}
      </div>
      <div className="d-flex justify-row">
        <input
          type="text"
          className="border p-2 mr-2 rounded w-[300px]"
          value={newMessage}
          onChange={(e) => setNewMessage(e.target.value)}
        />
        <button
          onClick={sendMessage}
          className="bg-blue-500 text-white p-2 rounded"
        >
          Send
        </button>
      </div>
    </div>
  );
 //...
Enter fullscreen mode Exit fullscreen mode

Lastly, update the globals.css file to remove all the default styles:

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Testing the application

We have successfully built a SignalR ASP.NET server and integrated it with our Next.js application. Now, run the Next.js application using the command:

npm runn dev
Enter fullscreen mode Exit fullscreen mode

Open your browser and visit http://localhost:3000/chat to see the frontend. Then, send messages to test the application: Real Time Chat App Demo Showing Message Being Sent In One Browser Window And Immediately Received In The Other

Challenges of integrating .NET real-time capabilities in a JavaScript environment

As we saw earlier, integrating a .NET-based real-time framework with a React-based framework has many benefits. However, it comes with its challenges, too. Let's explore some of these challenges and how SignalR addresses them.

Synchronizing state across different technologies

Maintaining a consistent state between the backend and frontend sides of an app can be very challenging — even more so in real-time apps where the state changes in response to data changes.

SignalR handles all the workloads with real-time capabilities that maintain both frontend and backend states by pushing updates directly from the server to the client.

Managing real-time data streams

Real-time data streams in chat apps or live dashboards can be one of the most complex things to deal with. Conventional HTTP requests run very slowly and can load to server overload.

SignalR communication is based on WebSockets, a protocol using full-duplex communication channels. This allows us to carry out an efficient data transfer over one single TCP connection.

Additionally, SignalR has fallbacks like long polling if WebSockets are not available. This behavior helps ensure the best possible real-time experience across various environments.

Scalability of real-time applications

Scaling real-time applications to handle a large number of concurrent users and requests can be challenging, but SignalR is developed to be scalable. You can integrate it with Azure SignalR Service, a fully managed service that can scale to handle any number of concurrent connections.

Learning curve and development complexity

Implementing real-time functionalities from scratch can be a challenge, and the complexities of learning new technologies slows down the app development process.

SignalR provides a simple API that allows developers to add real-time functionalities very easily without having to write custom code. This makes SignalR easier to use compared to alternatives like the Fluid Framework or WebRTC, which has some complexities when trying to get the code to work across different browsers.

Developers using WebRTC also have to write the code for the signaling functionalities. This is an important part of the WebRTC infrastructure that isn't standardized and therefore varies significantly between implementations.

WebRTC requires a solid understanding of server-side technologies and the ability to manage peer connections, media streams, and data channels effectively before you can work with it.

Conclusion

Congratulations! You have made it to the end of this tutorial. In this tutorial, we learned how to integrate Next.js and SignalR for enhanced real-time web app capabilities.

We started by combining SignalR and Next.js to build a real-time chat application. Then, we went further to explore the challenges and solutions of integrating .NET real-time capabilities within a JavaScript environment.

Lastly, we created a chat application to demonstrate the integration steps involved. Then, we developed an ASP.NET SignalR WebSocket server and interacted with it using the Next.js frontend we created. I hope this tutorial was helpful.


LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket Signup

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — start monitoring for free.

Top comments (0)