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#.
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:
There are also many other potential use cases for SignalR in various types of real-time apps.
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:
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.
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.
To get started, create a new ASP.NET web API with the command below:
dotnet new webapi -n real-time-app
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
The application will be built and run on http://localhost:5248/
:
You can preview the demo API on your browser by visiting the /weatherforecast
endpoint:
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();
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.
hub
classNow, 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); } }
In the code snippet above, we defined two WebSocket methods:
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.
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
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, install the SignalR package with the command below:
npm install @microsoft/signalr
Then create a cha
t 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; };
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;
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> ); //...
Lastly, update the globals.css
file to remove all the default styles:
@tailwind base; @tailwind components; @tailwind utilities;
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
Open your browser and visit http://localhost:3000/chat
to see the frontend. Then, send messages to test the application:
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.
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.
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.
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.
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.
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.
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 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.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]