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
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.
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);
}
}
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
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;
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
Open your browser and visit http://localhost:3000/chat
to see the frontend. Then, send messages to test the application:
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 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)