Building a realtime chat app with Next.js and Vercel
Tags: nextjs, vercel, softwaredevelopment, showdev
Published: 2024-02-06 10:55:35

_This post was originally written by **Devin Rader** on the [Ably Blog](https://hubs.la/Q01Ww2LK0)_

This post will walk through the creation of a realtime chat application with Next.js and deploying it to Vercel.

You'll learn how to:

- Create a brand new Next.js application
- Adding real-time functionality with Ably
- Create a Next.js Vercel Serverless API
- Use React Functional components and React Hooks with Ably
- Host your app on Vercel

Check out a [running version of the app](https://next-js-chat-app.vercel.app/) or [fork the app on GitHub](https://github.com/ably-labs/NextJS-chat-app).

## WebSockets in Vercel with Ably

Vercel allows users to deploy [Serverless Functions](https://vercel.com/docs/infrastructure), which are essentially just blocks of code that provide a response to an HTTP request. However, these functions have a maximum execution timeout, which means that it is not possible to maintain a WebSocket connection this way.

This is where Ably comes in. The client can connect to an [Ably Channel](https://hubs.la/Q01Ww2-90) and send and receive messages on it to add realtime functionality to your app by managing your WebSocket connections for you.

We’ll start by going over how to build an app that uses realtime functionality. If you prefer, you can [jump straight to how to use Ably with Vercel](https://hubs.la/Q024Nx3Y0).

> [Take a look at some of the challenges of implementing a reliable and highly-performant client-side WebSocket solution for JavaScript apps.](https://hubs.la/Q024Nxl90)

{% embed https://twitter.com/leeerob/status/1366799967088697344?ref_src=twsrc%5Etfw %}

## What are we going to build?

We’re building a realtime chat app that runs in the browser. It will be built upon the Next.js [create-next-app](https://nextjs.org/docs/pages/api-reference/create-next-app) template and contain a React component that uses Ably to send and receive messages. We'll also write a Next.js serverless function that will be used to connect to Ably.

The UI of the chat app we'll build. It is a window with speech bubbles for text.

![What are we going to build?](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/505jtdkvizekf3z4kpn1.png)

### Dependencies
To build this app, you’ll need:

- **An Ably account** for sending messages: [Create an account with Ably for free](https://hubs.la/Q01Ww3my0).
- **A Vercel account** for hosting on production: [Create an account with Vercel for free](https://vercel.com/signup).
- **Node 16.14** or greater: [Install Node](https://nodejs.org/en).

Once you have your Ably account you'll need an API key to authenticate with the Ably Service. To get an API key:

1. Visit your [app dashboard](https://hubs.ly/Q024NBjZ0) and click on "Create New App".
2. Give the new app a name.
3. Copy the Private API key once the app has been created. Keep it safe, this is how you will authenticate with the Ably service.

Vercel provides Next.js command line tools to help us. They don't need to be installed on your system as they're executed using `npx`.

## Scaffolding the Next.js Chat App

Start building the chat application by using `create-next-app` to scaffold a new Next.js-based application. In your terminal, type:

```
npx create-next-app@latest
```

Answer the prompts as follows:

```
What is your project named? nextjs-chat-app
Would you like to use TypeScript? No
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? No
Would you like to use `src/` directory? No
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias? No
What import alias would you like configured? @/*
```

After the prompts, `create-next-app` will create a folder with your project name and install the required dependencies. Change directories into your application directory:

```
cd nextjs-chat-app

```
Next, create a file called `.env` in the root of the directory. This is where we'll put the project's environment variables. Add your Ably API key to the `.env` file:

```
ABLY_API_KEY=your-ably-api-key:goes-here

```
Test that the application runs correctly by running the following command:

```
npm run dev

```
The Next.js dev server will spin up and you'll see an default Next.js starter app. This is what we'll build our chat app on top of.

## Realtime pub/sub messaging with Ably

The chat app will use [Ably](https://hubs.la/Q01XM6GG0) for [Publish/Subscribe (Pub/Sub messaging](https://hubs.la/Q023hMLP0) between the users. Pub/Sub is a popular pattern used for realtime data delivery. The app will be able to send, or `publish`, messages over an [Ably Channel](https://hubs.la/Q01Ww2-90). Clients that use the app will be `subscribed` to the channel and able to receive messages. We'll build a UI to create messages to be sent and to display messages as they are received.

## The realtime chat app architecture

The topology of our Next.js app will look like this:

```javascript
├─ .env
├─ .gitignore
├─ package-lock.json
├─ package.json
├─ README.md   
|    
├─── components
│     ├─ Chat.jsx
|     ├─ ChatBox.jsx
│     ├─ ChatBox.module.css
|
├─── app
│    ├─ globals.css
│    ├─ layout.js
│    ├─ page.js
│    │   
│    └─── api
│          └─ route.js
│           
└─── public
```

- `/app/page.js` is the home page
- `/app/api/route.js` is our Ably token authentication API
- `/components/Chat.jsx` is the main chat host component
- `/components/ChatBox.jsx` the component that contains the actual chat UI
- `/components/ChatBox.module.css` contains the styles for the chat component

Let's walk through how this application is built.

## Authenticating an Ably client
There are a number of ways to [authenticate an Ably client](https://hubs.la/Q023h-mm0). For our application, we’ll use [token authentication](https://hubs.la/Q024NLGy0). Token authentication uses a trusted device with an [API key](https://hubs.la/Q023j0tY0), in our case an API route that we’ll build using Next.js, to issue time-limited tokens to untrusted clients.

In version 13, Next.js introduced the new App Router. Built on [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) it supports shared layouts, nested routing, loading states, error handling, and more including API routes via its [Route Handler](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) feature. Let's build that API route.

Start by installing the [Ably npm package](https://www.npmjs.com/package/ably) in the root of your new app:

```
npm install ably
```

Next, create a file called `./app/api/route.js.` Add the following code:

```
import Ably from "ably/promises";

export async function GET(request) {
    const client = new Ably.Realtime(process.env.ABLY_API_KEY);
    const tokenRequestData = await client.auth.createTokenRequest({ clientId: 'ably-nextjs-demo' });
    return Response.json(tokenRequestData);
};
```
This function uses the Ably SDK to create a tokenRequest with your API key. The token will be used later - it allows you to keep your "real" API key safe while using it in the Next.js app. By default, this API is configured to be available on [http://localhost:3000/api/](http://localhost:3000/api/). We're going to provide this URL to the Ably SDK in our client to authenticate with Ably.

## Next.js Server vs Client Components

Before we start creating pages in our application, it's important to understand how Next.js renders content. The framework supports multiple rendering methods including server-side rendering (SSR), static site rendering (SSG), and client-side rendering (CSR). There are many pros and cons to each rendering method (too many to cover in this post) so if these concepts are new to you, Google’s [web.dev](https://web.dev/) site has a very good introduction to [rendering on the web](https://web.dev/rendering-on-the-web/#server-side-rendering) that can help you understand rendering options.

Next.js defaults to server-side rendering meaning the page HTML is generated on the server for each request and sent to the browser. This means that by default when you create a new component in your React app, you are creating a [Server Component](https://nextjs.org/docs/app/building-your-application/rendering/server-components).

Server Components offer a lot of benefits, but one drawback is that they don’t have access to browser APIs. To allow you to create components that can access those APIs Next.js also includes the ability to create [Client Components](https://nextjs.org/docs/app/building-your-application/rendering/client-components).

To indicate that you want a component to be a Client Component, you use React’s `use client` directive at the top of your component. Doing this tells React that it should render the component on the client side.

When using Next.js however even for components marked with `use client` the framework will try to [render a static preview of the component on the server](https://nextjs.org/docs/app/building-your-application/rendering/client-components). This can cause problems when creating a component that attempts to instantiate an instance of an Ably client.

To avoid this, you can use [Lazy Loading](https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading) to defer loading of Client Components.

## Building the components

Pages in `Next.js` are React components, so the `app/page.js `home page is the React component that contains the page layout.

Open `page.js` and replace the default scaffolded content with this markup:

```
import Head from 'next/head';

export default function Home() {
  return (
    <div className="container">
      <Head>
        <title>Realtime Chat App with Ably, NextJS and Vercel</title>
        <link rel="icon" href="https://static.ably.dev/motif-red.svg?nextjs-vercel" type="image/svg+xml" />
      </Head>
      <main>
        <h1 className="title">Next.js Chat Demo</h1>
        {/* Insert Chat Component */}
      </main>
      <footer>
        Powered by&nbsp;<a href="https://vercel.com" target="_blank" rel="noopener noreferrer">Vercel</a>&nbsp;and&nbsp;<a href="https://ably.com" rel="noopener noreferrer">Ably</a>
        <a href="https://github.com/ably-labs/NextJS-chat-app" className="github-corner" aria-label="View source on GitHub">
          <svg width="80" height="80" viewBox="0 0 250 250" className="svg" aria-hidden="true">
            <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
            <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" className="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" className="octo-body"></path></svg>
            </a>
      </footer>
    </div>
  )
}
```
Next, in the same app directory, locate the `globals.css` file and replace its contents with the [basic CSS styles we’ll use in our application](https://github.com/ably-labs/NextJS-chat-app/blob/main/app/globals.css) which you can find in the projects GitHub repository.

Run the app. You should see this:


![App preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2fnvye97aix4p0djvyaf.png)

Next, in the root of your application create a new `components` folder and within create a new React component file named `Chat.jsx`. Scaffold the initial component using the code below:

```
'use client'

import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';

export default function Chat() {

  const client = Ably.Realtime.Promise({ authUrl: '/api' })

  return (
    <AblyProvider client={ client }>
    </AblyProvider>
  )
}
```
This component is marked with the `use client` directive. It will be the boundary between SSR components and CSR components.

The component imports the Ably and Ably React paths from the Ably package and uses them to create a new instance of an Ably client, authenticating it against the API endpoint we created above and then providing that instance to an `<AblyProvider>` component. `<AblyProvider>` will retain the client in the application context so that any child component can access it.

To use our new Chat component we’ll dynamically import it into our home page at the top of `page.js`:

```
import Head from 'next/head';
import dynamic from 'next/dynamic';

const Chat = dynamic(() => import('../components/Chat'), {
  ssr: false,
})
```
And add the component to the markup:

```
<main>
  <h1 className="title">Next.js Chat Demo</h1>
  <Chat />
</main>
```
Validate that the application works properly by running the project again. You won’t see any difference in the UI yet, but we’ll get to that in the next section.

## Writing the chat component logic

The chat app logic is contained inside a new `ChatBox` component. To create this start by creating two new files: `ChatBox.jsx` which will hold the React component and `ChatBox.module.css`, a stylesheet we’ll use to style the ChatBox component.

As we did above with `globals.css` you can get the CSS needed to style the ChatBox component by copying into it the styles which you can find in the [projects Github repository](https://github.com/ably-labs/NextJS-chat-app/blob/main/components/ChatBox.module.css).

Next, open the `Chatbox.jsx` file and add the import statements we'll need at the top of the file:

```
import React, { useEffect, useState } from 'react';
import { useChannel } from "ably/react";
import styles from './ChatBox.module.css';
```

Define a `ChatBox` function that is exported as a React component. Add to the function variables to store references to some of the HTML elements in the code.

```
export default function ChatBox() {

  let inputBox = null;
  let messageEnd = null;

  return ()
}
```
Set up the state properties used in the component:

```
const [messageText, setMessageText] = useState("");
const [receivedMessages, setMessages] = useState([]);     
const messageTextIsEmpty = messageText.trim().length === 0;
```

- `messageText` will be bound to a textarea element where messages can be typed
- `receiveMessages` stores the on-screen chat history
- `messageTextIsEmpty` is used to disable the send button when the textarea is empty

Now we'll use the `useChannel` hook that we imported earlier.
useChannel is a [react-hook](https://legacy.reactjs.org/docs/hooks-intro.html) API for subscribing to messages from an Ably channel. You provide it with a channel name and a callback to be invoked whenever a message is received.

Both the channel instance and the Ably JavaScript SDK instance are returned from useChannel.

```
const { channel, ably } = useChannel("chat-demo", (message) => {
  const history = receivedMessages.slice(-199);
  setMessages([...history, message]);
});
```

Here we're computing the state that'll be drawn into the [message history](https://hubs.la/Q01Ww98s0). We do that by slicing the last 199 messages from the `receivedMessages` buffer.

Then we take the message history and combine it with the new message. This means we'll always have up to 199 messages + 1 new message, stored using the `setMessages` React `useState` hook.

Next, we need to handle the UI interactions by defining a few functions.

First, there's `sendChatMessage`, which is responsible for publishing new messages. It uses the Ably Channel returned by the useChannel hook, clears the input, and focuses on the textarea so that users can type more messages:

```
const sendChatMessage = (messageText) => {
  channel.publish({ name: "chat-message", data: messageText });
  setMessageText("");
  inputBox.focus();
}
```

Then `handleFormSubmission`, which is triggered when the submit button is clicked and calls `sendChatMessage`, along with preventing a page reload:

```
const handleFormSubmission = (event) => {
  event.preventDefault();
  sendChatMessage(messageText);
}
```
Finally the `handleKeyPress` event is wired up to make sure that if a user presses the enter key, while there is text in the textarea, the `sendChatMessage` function is triggered.

```
const handleKeyPress = (event) => {
  if (e.charCode !== 13 || messageTextIsEmpty) {
    return;
  }
  sendChatMessage(messageText);
  event.preventDefault();
}
```
Next, we need to construct the UI elements to display the messages. To do this, we will [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) the received Ably messages into HTML span elements:

```
const messages = receivedMessages.map((message, index) => {
  const author = message.connectionId === ably.connection.id ? "me" : "other";
  return <span key={index} className={styles.message} data-author={author}>{message.data}</span>;
});
```
In order to keep the message box scrolled to the most recent message (the one on the bottom) we'll need to add an empty div element into the message container, which will then be scrolled into view whenever the components re-renders. This is the element that we'll add to the UI later:

```
<div ref={(element) => { messageEnd = element; }}></div>
```
We use a `useEffect` hook along with `scrollIntoView()` to scroll the message history to the bottom whenever the component renders.

```
useEffect(() => {
messageEnd.scrollIntoView({ behaviour: "smooth" });
});
```

Finally, we will write the React component markup with the event handlers all bound to the `onChange` and `onKeyPress` events in JSX.

The markup itself is just a few div elements and a form with a textarea for user input. There are two calls to the react `ref` function, which allows us to capture a reference to the elements when they are rendered so that we can interact with them in JavaScript. The returned markup will look like this:

```
return (
  <div className={styles.chatHolder}>
    <div className={styles.chatText}>
      {messages}
      <div ref={(element) => { messageEnd = element; }}></div>
    </div>
    <form onSubmit={handleFormSubmission} className={styles.form}>
      <textarea
        ref={(element) => { inputBox = element; }}
        value={messageText}
        placeholder="Type a message..."
        onChange={e => setMessageText(e.target.value)}
        onKeyPress={handleKeyPress}
        className={styles.textarea}
      ></textarea>
      <button type="submit" className={styles.button} disabled={messageTextIsEmpty}>Send</button>
    </form>
  </div>
  )
```
Add the ChatBox component to the Chat component

```
'use client'

import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import ChatBox from './ChatBox.jsx';

const client = Ably.Realtime.Promise({ authUrl: '/api' })

export default function Chat() {
  return (
    <AblyProvider client={ client }>
      <ChatBox />
    </AblyProvider>
  )
}
```
Swap over to your browser (make sure you restart the application server if you need to) and you should see a functioning chat application now.

## Hosting on Vercel

We're using Vercel as our development server and build pipeline.

> The easiest way to deploy Next.js to production is to use the Vercel platform from the creators of Next.js. Vercel is an all-in-one platform with Global CDN supporting static & Jamstack deployment and Serverless Functions.
> -- [The Next.js documentation](https://nextjs.org/docs/deployment)

In order to deploy your new chat app to Vercel you'll need to:

- Create a [GitHub account](https://github.com/) (if you don't already have one)
- [Push your app to a GitHub repository](https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/creating-a-new-repository)
- [Create a Vercel account](https://vercel.com/signup)
- Create a new Vercel app and import your app from your GitHub repository. (This will require you to authorize Vercel to use your GitHub account)
- Add your `ABLY_API_KEY` as an environment variable
- Watch your app deploy
- Visit the newly created URL in your browser!

## Make it your own

There are a few ways that this example could be extended:

### Add message history
There is currently no chat history in this demo, you'll only see messages that come in after you join the chat. You could expand this demo by using [Ably's rewind feature](https://hubs.la/Q024PH0m0) for up to two minutes of history for free, or with a paid account, for up to ~48 hours.

### Add user names

There aren't any usernames sent with the chat messages. This demo could be extended to introduce a username input box, and to add the current username to messages as they're sent.

The demo uses the randomly generated Ably client Id as a unique identifier - which is how it can detect if it is "me" or "someone else" who sent the message.

## Let us know

Hope you have enjoyed this tutorial. If you are interested in learning more about realtime in Next.js, check out these resources:

- [Building a realtime SMS voting app... In the web](https://hubs.la/Q024PKTT0)
- [Show SMS Notifications in the Browser with Next.JS, Ably, and Vercel](https://learn.vonage.com/blog/2021/04/27/show-sms-notifications-in-the-browser-with-next-js-ably-and-vercel/)

If this tutorial was helpful, or you're using Next.js and Ably in your project, we'd love to hear about it. [Drop us a message](https://twitter.com/ablyrealtime) on Twitter or email us at devrel@ably.com.