Build Live Cursors with Next.js, RPC and Durable Objects
In this tutorial, you will learn how to build a real-time Next.js ↗ app that displays the live cursor location of each connected user using Durable Objects, the Workers' built-in RPC (Remote Procedure Call) system, and the OpenNext ↗ Cloudflare adapter.
The application works like this:
- An ID is generated for each user that navigates to the application, which is used for identifying the WebSocket connection in the Durable Object.
- Once the WebSocket connection is established, the application sends a message to the WebSocket Durable Object to determine the current number of connected users.
- A user can close all active WebSocket connections via a Next.js server action that uses an RPC method.
- It handles WebSocket and mouse movement events to update the location of other users' cursors in the UI and to send updates about the user's own cursor, as well as join and leave WebSocket events.
-
Run the following command to create your Next.js Worker named
next-rpc
:For setup, select the following options:
- For What would you like to start with?, choose
Framework Starter
. - For Which development framework do you want to use?, choose
Next.js
. - Complete the framework's own CLI wizard.
- For Do you want to use git for version control?, choose
Yes
. - For Do you want to deploy your application?, choose
No
(we will be making some changes before deploying).
- For What would you like to start with?, choose
-
Change into your new directory:
-
Install nanoid ↗ so that string IDs can be generated for clients:
-
Install perfect-cursors ↗ to interpolate cursor positions:
-
Define workspaces for each Worker:
Update your
package.json
file.Create a new file
pnpm-workspace.yaml
.
This Worker will manage the Durable Object and also have internal APIs
that will be made available to the Next.js Worker using a WorkerEntrypoint
class.
-
Create another Worker named
worker
inside the Next.js directory:For setup, select the following options:
- For What would you like to start with?, choose
Hello World example
. - For Which template would you like to use?, choose
Hello World Worker using Durable Objects
. - For Which language do you want to use?, choose
TypeScript
. - For Do you want to use git for version control?, choose
Yes
. - For Do you want to deploy your application?, choose
No
(we will be making some changes before deploying).
- For What would you like to start with?, choose
-
In your
worker/wrangler.toml
file, update the Durable Object binding: -
Initialize the main methods for the Durable Object and define types for WebSocket messages and cursor sessions in your
worker/src/index.ts
to support type-safe interaction:WsMessage
. Specifies the structure of WebSocket messages handled by the Durable Object.Session
. Represents the connected user's ID and current cursor coordinates.
Now update
worker-configuration.d.ts
by running: -
Update the Durable Object to manage WebSockets:
- The main
fetch
handler routes requests with a/ws
URL to theCursorSessions
Durable Object where a WebSocket connection is established. - The
CursorSessions
class manages WebSocket connections, session states, and broadcasts messages to other connected clients.- When a new WebSocket connection is established, the Durable Object broadcasts a
join
message to all connected clients; similarly, aquit
message is broadcast when a client disconnects. - It tracks each WebSocket client's last cursor position under the
move
message, which is broadcasted to all active clients. - When a
get-cursors
message is received, it sends the number of currently active clients to the specific client that requested it.
- When a new WebSocket connection is established, the Durable Object broadcasts a
- The main
-
Extend the
WorkerEntrypoint
class for RPC: -
Leave the Durable Object Worker running. It's used for RPC and serves as a local WebSocket server:
-
Use the resulting address from the previous step to set the Worker host as a public environment variable in your Next.js project:
-
In your Next.js
wrangler.toml
file, declare the external Durable Object binding and the Service binding toSessionsRPC
: -
Update your
env.d.ts
file for type-safety: -
Include Next.js server side logic:
- Add a server action to close all active WebSocket connections.
- Use the RPC method
closeSessions
from theRPC_SERVICE
Service binding instead of invoking the Durable Object RPC method because of the limitation mentioned in the note above. - The server component generates unique IDs using
nanoid
to identify the WebSocket connection within the Durable Object. - Set the
dynamic
↗ value toforce-dynamic
to ensure unique ID generation and avoid static rendering
-
Create a client component to manage WebSocket and mouse movement events:
src/app/cursor.tsx
The generated ID is used here and passed as a parameter to the WebSocket server:
The component starts the WebSocket connection and handles 4 types of WebSocket messages, which trigger updates to React's state:
join
. Received when a new WebSocket connection is established.quit
. Received when a WebSocket connection is closed.move
. Received when a user's cursor moves.get-cursors-response
. Received when a client sends aget-cursors
message, which is triggered once the WebSocket connection is open.
It sends the user's cursor coordinates to the WebSocket server during the
mousemove
↗ event, which then broadcasts them to all active WebSocket clients.Although there are multiple strategies you can use together for real-time cursor synchronization (e.g., batching, interpolation, etc.), in this tutorial throttling, spline interpolation and position normalization are used:
Each animated cursor is controlled by a
PerfectCursor
instance, which animates its position along a spline curve defined by the cursor's latest positions: -
Run Next.js development server:
-
Open the App in the browser.
-
Change into your Durable Object Worker directory:
Deploy the Worker:
Copy only the host from the generated Worker URL, excluding the protocol, and set
NEXT_PUBLIC_WS_HOST
in.env.local
to this value (e.g.,worker-unique-identifier.workers.dev
). -
Change into your root directory and deploy your Next.js app:
In this tutorial, you learned how to integrate Next.js with Durable Objects to build a real-time application to visualize cursors. You also learned how to use Workers' built-in RPC system alongside Next.js server actions. The complete code for this tutorial is available on GitHub ↗.
You can check other Cloudflare tutorials or related resources:
- Workers RPC.
- Next.js and Workers Static Assets.
- Build a seat booking app with SQLite in Durable Objects.