惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
Google DeepMind News
Google DeepMind News
Hugging Face - Blog
Hugging Face - Blog
D
Docker
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
J
Java Code Geeks
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Simon Willison's Weblog
Simon Willison's Weblog
S
Security Affairs
NISL@THU
NISL@THU
T
Tor Project blog
A
About on SuperTechFans
宝玉的分享
宝玉的分享
腾讯CDC
S
Schneier on Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
P
Privacy & Cybersecurity Law Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Stack Overflow Blog
Stack Overflow Blog
P
Privacy International News Feed
雷峰网
雷峰网
C
Cyber Attacks, Cyber Crime and Cyber Security
Vercel News
Vercel News
Cisco Talos Blog
Cisco Talos Blog
D
DataBreaches.Net
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
L
LINUX DO - 热门话题
Microsoft Security Blog
Microsoft Security Blog
Latest news
Latest news
C
Check Point Blog
有赞技术团队
有赞技术团队
T
The Exploit Database - CXSecurity.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
云风的 BLOG
云风的 BLOG
SecWiki News
SecWiki News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
爱范儿
爱范儿
月光博客
月光博客
V
Vulnerabilities – Threatpost
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
T
The Blog of Author Tim Ferriss
C
Cisco Blogs
Webroot Blog
Webroot Blog
S
Security @ Cisco Blogs

Clerk Blog

Going to production with Clerk Deploy Clerk Init: The fastest way to start a new project Introducing Clerk CLI Middleware-based route protection bypass Postmortem: Clerk System Outage (March 10, 2026) Clerk for the AI era Add API Key support to your SaaS in minutes Postmortem: Clerk System Outage (February 19, 2026) Using Clerk in a React Native app Postmortem: DNS Provider Outage (February 10, 2026) How do I implement passkeys in Next.js? Clerk ranked #4 fastest-growing software vendor on Ramp’s December 2025 list How do I handle JWT verification in Next.js? Committing to Agent Identity: Clerk raises $50m Series C from Menlo and Anthropic’s Anthology Fund What is the best way to handle authentication in Next.js App Router? Postmortem: Database Incident (September 14–18, 2025) How do I add authentication to a Next.js app? Introducing Free Trials in Clerk Billing Postmortem: August 28, 2025 - elevated API latency and errors Introducing Mosaic: Bring Your Brand to Every Authentication Flow Multi-tenant authentication: What you need to know (and how Clerk helps) What are the risks and challenges of multi-tenancy? Resilience in Practice: Regional Failover at Clerk Build a Cross-Platform B2B App with Clerk, Expo, and Supabase Highlights from the MiduDev/Clerk Hackathon Add multi-tenancy to an app built with Clerk, Lovable, and Supabase How to build an AI coding rules app with Clerk, Lovable, and Supabase How to Build Multi-Tenant Authentication with Clerk Choosing the right SaaS architecture: Multi-Tenant vs. Single-Tenant Postmortem: June 26, 2025 service outage How to Design a Multi-Tenant SaaS Architecture What is multi-tenancy and why it matters for B2B SaaS How OAuth Works Synchronize user data from Clerk to Supabase Add subscriptions to your SaaS with Clerk Billing Getting started with Clerk Billing Multi-tenant analytics with Tinybird and Clerk How Huntr Migrated 250K Users to Clerk: A Scalable Auth Solution for Startups How to take Clerk to Production How to take your Clerk application to production A practical guide to testing Clerk Next.js applications Implementing multi-tenancy into a Supabase app with Clerk How Clerk integrates with a Next.js application using Supabase How Clerk integrates with Supabase Build a blog with tRPC, Prisma, Next.js and Clerk How to enrich PostHog events with Clerk user data How to build a secure project management platform with Next.js, Clerk, and Neon Validate your SaaS idea while building an audience Postmortem: February 6, 2025 service outage Implement Role-Based Access Control in Next.js 15 Build a Next.js sign-up form with React Hook Form Build a Next.js login page template How to implement Google authentication in Next.js 15 What is middleware in Next.js? How to customize Next.js metadata How to set environment variables in Node.js Building a React Login Page Template How to implement per-user OAuth scopes with Clerk Using Clerk SSO to access Google Calendar and other service data Streamline enterprise customer onboarding with SAML and Clerk Clerk launches EASIE SSO and eliminates SSO fees How to secure Liveblocks Rooms with Clerk in Next.js Securing Node.js Express APIs with Clerk and React Combining the benefits of session tokens and JWTs Build a task manager with Next.js, Supabase, and Clerk Comparing Clerk Webhooks vs Backend API Automate Neon schema changes with Drizzle and GitHub Actions A guide to reading authenticated user data from Clerk Role based access control with Clerk Organizations Mitigating OAuth’s recently discovered Open Response Type vulnerability Per-user B2B monetization with Stripe and Clerk Organizations Build a team-based task manager with Next.js, Neon, and Clerk Building a Hybrid Sign-Up/Subscribe Form with Stripe Elements Welcoming Colin from Zod as our inaugural Open Source Fellow Build a waitlist with Clerk user metadata How to use Clerk with PostHog Identify in Next.js How to secure API Gateway using JWT and Lambda Authorizers with Clerk What are passkeys and how do they work? Comparing Authentication in React.js vs. Next.js How to Add an Onboarding Flow for your Application with Clerk Create Your Own Custom User Menu with Radix - Part 2 Introducing Webhook Workflows with Inngest & Svix Clerk raises $30M Series B from CRV and Stripe Clerk in 2023: A Year in Review Build a Movie Emoji Quiz App with Remix, Fauna, and Clerk Ultimate Guide to Magic Link Authentication Create Your Own Custom User Menu with Radix Introducing has(), protect(), and <Protect> Updated Pricing: 10,000 MAUs Free, and a new “Pro Plan” Next.js Authentication with Clerk: Streamlined SSR Handling Clerk Webhooks: Data Sync with Convex Exploring Clerk Metadata with Stripe Webhooks The Ultimate Guide to Next.js Authentication Empower Your Support Team With User Impersonation Clerk Webhooks: Getting Started A Complete Guide to Session Management in Next.js The Advanced Guide to Passwordless Authentication in Next.js How We Roll – Chapter 10: Roundup How We Roll – Chapter 9: Infrastructure Password-Based Authentication in Next.js
Build a modern authenticated chat application with Next.js, Ably, and Clerk
Alex Booker · 2024-06-04 · via Clerk Blog

This 6000-word mega-post teaches you how to code an authenticated chat application with roles and permissions from scratch!

You will learn how to implement message transmission over WebSockets and a dynamic "who's online?" list that updates in realtime.

We'll also implement a secure moderator role that enables select users to access restricted channels like "#moderators-only" and delete unwanted messages.

Authenticated Next Chat App With Ably And Clerk guide illustration

As for the technology stack, it's React on the frontend using Tailwind and shadcn/ui for styling. We'll integrate Ably for serverless WebSockets and Clerk for authentication and user management.

Just want the code?

I feel that! You can find the complete source code on my GitHub.

If you want to run the project locally, I've included the minimal necessary steps in the repository README for you to reference.

Before you begin

Before delving in, it would be good if you had an understanding of the following technologies and concepts:

  • React
    • Components and how to think in React
    • A grasp of useState and how to, for example, implement a form component from scratch
    • Familiarity with useEffect, useRef, and useReducer
  • Next.js
    • How to define routes with App Router
    • Awareness of client components vs server components
  • CSS
    • An awareness of Flexbox and Grid

Start here

First, let's create a blank Next.js App Router project called chat-tutorial.

The easiest way is with create-next-app:

terminal

Run the above command then cd into the newly-created directory:

terminal

Create the chat layout

Before we go too far, let's first create the layout for our chat application.

Open the chat-tutorial directory in your editor of choice and add a nav to src/app/layout.js:

src/app/layout.js

Next, create src/app/chat/[[...channelName]]/page.js:

src/app/chat/[[...channelName]]/page.js

Run the development server with npm run dev, open your browser, then navigate to /chat to observe the three-column skeleton:

Authenticated Next Chat App With Ably And Clerk guide illustration

In the upcoming steps, we'll populate each column with the channel list, chat, and online list components respectively.

Note that /chat is a dynamic route with an optional route segment called channelName .

We'll reference this segment by params.channelName and use the value do determine what channel the user is currently participating in.

Install shadcn/ui

If you haven't come across shadcn/ui before, this tool enables design-impaired JavaScript developers like me to pick and choose from an assortment of beautiful React components that are also accessible and easy to customize. 😅

Still inside the chat-tutorial directory, run:

terminal

You'll be presented with 3 prompts. Answer like this:

  • Which style would you like to use? Default
  • Which color would you like to use as base color? Slate
  • Would you like to use CSS variables for colors? Yes

Build the chat React components

In this section, we'll build the MessageInput and MessageList components.

To make them look sleek without writing much code, let's lean on the shadcn/ui's ready-made Input and Avatar components.

To download them, run the following commands:

terminal

Create src/app/[[...channel]]/chat/message-input.js:

src/app/[[...channel]]/chat/message-input.js

Finally, create src/app/[[...channel]]/chat/message-list.js:

src/app/[[...channel]]/chat/message-list.js

Realtime messaging with Ably

To enable realtime messaging and store message history, we'll use a serverless WebSockets platform called Ably.

Authenticated Next Chat App With Ably And Clerk guide illustration

Because Ably manages the WebSockets, the connection is highly reliable. Additionally, using a hosted WebSocket platform allows us to benefit from realtime communication, even when deploying to serverless platforms such as Vercel that don't typically support long-lived WebSocket connections.

With Ably, events are published to "topics" (named logical channels). Subscribers receive all messages published to the topics to which they subscribe.

Authenticated Next Chat App With Ably And Clerk guide illustration

Conceptually, this maps very tidily in chat application where we utilize an Ably topic per chat channel.

When the user navigates routes, the channelName segment is updated. We can reference channelName to connect to an Ably topic by the same name prefixed with chat:, like so:

The prefix part of the Ably topic is called a namespace. Namespaces allow us to logically group related channels. This grouping will come in handy later when we apply Ably access rules for all chat:* channels.

The prefix chat: is one I chose arbitrarily and doesn't mean anything specific in Ably.

Implement basic Ably chat messaging

If you haven't already, sign-up to Ably then Create new app with the following options:

  • App name: Call your app something meaningful
  • Select your preferred language(s): JavaScript
  • What type of app are you building? Live Chat

Authenticated Next Chat App With Ably And Clerk guide illustration

In the next screen, copy or otherwise note your Ably API key - we'll need it momentarily:

Authenticated Next Chat App With Ably And Clerk guide illustration

Back in the terminal, install the Ably React SDK:

terminal

Create src/app/[[...channel]]/chat/chat.js:

src/app/[[...channel]]/chat/chat.js

Finally, update src/app/[[...channel]]/chat/page.js.

Take care to replace YOUR_ABLY_API_KEY with the API key you noted a moment ago:

src/app/[[...channel]]/chat/page.js

With that, the basic chat is ready to test!

Open /chat/general in a couple of localhost browser windows to send and receive chat messages over WebSockets:

Before we go any further, there is something very important to note about the code above!

In the excerpt, we instantiate an Ably Realtime instance with the API key. We also hard-coded the clientId.

src/app/[[...channel]]/chat/page.js

This is problematic:

  • An Ably API key is top secret and should never (ever) go in production client-side code as a malicious actor could easily find it and exploit your application. I have done so here only for demonstration purposes.
  • Every client should have a unique identifier based on the user's name.

In an upcoming section, we will address these concerns by implementing a secure backend endpoint to generate an Ably-compatible JWT token which includes verified information about the user's identity and their capabilities.

Implement a chat channel-switcher

To help users discover channels and navigate them more easily, let's create a ChannelList component and slot it into the left-hand column of our layout.

First, run this command to install clsx, a handy module for conditionally applying Tailwind styles:

terminal

Next, create src/app/[[...channel]]/chat/channel-list.js:

src/app/[[...channel]]/chat/channel-list.js

Finally, update src/app/[[...channel]]/chat/page.js:

src/app/[[...channel]]/chat/page.js

Using clsx, we conditionally bolden the current channel to convey the channel they're currently chatting in.

Here, I hardcoded a channels list but this same data structure could come from a database should you want to enable users to create channels dynamically.

Return to the browser and check it out - the user can now switch channels without fumbling with the URL in the address bar:

Authenticated Next Chat App With Ably And Clerk guide illustration

Authentication with Clerk

With basic realtime messaging in place, it's time to authenticate Next.js users with Clerk.

Authenticated Next Chat App With Ably And Clerk guide illustration

Clerk is an authentication and user management platform with readyade React components and seamless Next.js authentication middleware.

With Clerk, you get an assortment of beautifully-designed React components. Some are UI components like SignInButton and UserButton, while others are "helper components" that conditionally render children based on some authentication or authorization state.

Authenticated Next Chat App With Ably And Clerk guide illustration

It's typical to use both kinds of components, and they make adding fully-featured authentication to Next.js a lot smoother than you might be expecting if you have experience rolling your own auth.

User management

In addition to authentication, Clerk handles user management. This means when a user signs up to your application, Clerk stores and manages a user record with information such as the their full name, avatar, and metadata.

In the next sections, we'll utilize this information from Clerk to identify the Ably client by the user's unique identifier instead of the hardcoded value of "Alex" we have been relying on until now.

The Clerk dashboard UI provides a convenient user management backoffice to manage user's information such as their role, without having to implement commands or a custom backend UI.

For the purposes of this tutorial, we'll update the user's public metadata with an isMod property that denotes the user's role in the chat application (more on that soon).

Authenticated Next Chat App With Ably And Clerk guide illustration

Configure Clerk

If you haven't already, create a Clerk account. Clerk doesn't ask for a credit card and you get 10,000 monthly active users for free.

Next, create a Clerk application with username, phone, and email sign-in options:

Authenticated Next Chat App With Ably And Clerk guide illustration

Still in the Clerk dashboard, under User & Authentication and Email, Phone, Username, scroll to Personal information and enable Name:

Authenticated Next Chat App With Ably And Clerk guide illustration

Under Customization and Avatars, enable Initials:

Authenticated Next Chat App With Ably And Clerk guide illustration

If a user doesn't have an avatar, Clerk will generate one based on their initials and the styling options you choose. I set the background to Solid and chose the color #BDBDBD but you can make this your own!

Next, under API Keys, take advantage of the handy Quick Copy box to copy your Clerk environment variables.

Authenticated Next Chat App With Ably And Clerk guide illustration

Create a file called ./.env.local and paste them in.

It'll look something like this:

.env.local

Implement authentication with Clerk

To start authenticating Next.js users with Clerk, we'll first need to install the Clerk Next SDK:

terminal

Next, create src/middleware.js:

src/middleware.js

This middleware makes it so that only authenticated users can access routes starting with /chat.

If the user isn't authenticated, they'll be redirected to the Clerk account portal to sign-up or sign-in.

Next, update src/app/layout.js:

src/app/layout.js

Here, we utilize some of those Clerk React components I mentioned before:

  • <ClerkProvider /> wraps your React app to provide session and user context.
  • <SignInButton /> links to the sign-in page or shows the sign-in modal.
  • <UserButton /> renders the user's avatar with a dropdown for account management.
  • <SignedIn /> renders children only if a user is signed in.
  • <SignedOut /> renders children only if no user is signed in.
  • <ClerkLoaded /> ensures the Clerk object is loaded and accessible.

Let's see it all in action.

Open /chat/general and you'll be redirected to the Clerk account portal to sign-in.

Authenticated Next Chat App With Ably And Clerk guide illustration

Sign-in successfully and Clerk will redirect the user back to /chat/general where they'll have full access to the chat!

Note the <UserButton /> in the top-right corner, designed to resemble the user button UI popularized by Google:

Authenticated Next Chat App With Ably And Clerk guide illustration

Using Ably and Clerk together

So far, we have a functioning chat application and we have a way to authenticate users but these two parts of the app aren't really connected:

  • Even though the user's signed in under their own name, every message is still technically coming from "Alex" with the "AL" avatar we hard-coded earlier. Now that we know the identity of the user, we'll need to pass that on to Ably instead of the hard-coded value.
  • Even though the Next.js pages are protected behind a sign-in form, the underlying Ably topic is technically accessible to anyone. That isn't necessarily a problem for public channels like "general", but the "mods-only" channel includes sensitive messages and access should be carefully restricted. Now that we have a means to authenticate users with Clerk, let's authorize their ability to connect to restricted channels.

Understanding tokens

As a reminder, earlier, we hard-coded the Ably API key in the client-side code:

src/app/[[...channel]]/chat/page.js

This made it convenient to connect to Ably for the purposes of this tutorial, but it's neither secure nor flexible.

In the upcoming section, we'll remove the hard-coded values. Instead, we'll point Ably at an authentication backend endpoint poised to return an Ably-compatible JWT token.

Before we write any code on that front, let's take a moment to understand Ably-compatible JWT tokens and the token flow at high level. It will make the code in the next section a lot easier to understand.

An Ably-compatible JWT token contains three crucial pieces of information:

  1. The identity (clientId) of the authenticated user (according to Clerk and the Clerk Next.js middleware).
  2. The capabilities this user has in which Ably channels, dependent on their role.
  3. Additional claims, such as isMod.

Instead of passing a hard-coded clientId and key to Ably, we'll point the Ably JavaScript SDK to the backend endpoint we're about to implement.

Equipped with the backend authentication endpoint, the Ably SDK will automatically request the token before using it to connect to the service.

Authenticated Next Chat App With Ably And Clerk guide illustration

Ably uses this token for two discrete purposes:

  • Capabilities: A token contains information about the user's Ably capabilities such as their capability to subscribe to messages in a topic named "chat:mods-only". If the user doesn't have the capability to a restricted topic like "chat:mods-only", Ably rejects the connection and the Ably client propagates a connection error.
  • Claims: A token also includes claims. When Ably learns of a client's claims, the service will automatically add those claims to the metadata of events published by the authenticated client. These claims can be read by the client to authorize actions such as deleting or editing a message.

While capabilities and claims can sound similar, they are two discrete ideas in the Ably world.

We use capabilities to allow or disallow native Ably operations like subscribing or publishing to/on a specific topic or set of topics.

Capabilities, on the other hand, are metadata encoded in the token and associated with the connected client and any events they publish. Capabilities are used to authorize bespoke functionality like message deletions or edits.

Create an Ably token Next endpoint

First things first, copy the Ably API key from earlier into .env.local.

You can copy the key over from src/app/[[...channel]]/chat/page.js or fetch it from the Ably dashboard again:

.env.local

Going forward, we'll only reference the environment variable (we'll remove it from the client-side code in due course).

Next, install jose, a JWT helper library:

terminal

Once jose installs, create src/app/api/ably/route.js:

src/app/api/ably/route.js

This is the backend authentication endpoint. It utilizes Clerk's currentUser function to access information about the currently-authenticated user, including their ID. This user ID is used for the Ably clientId, ensuring every Ably client has a unique identifier.

If the user is a moderator according to the user's publicMetadata, we give them unrestricted Ably capabilities with *. Otherwise, they are limited to 3 channels:

src/app/api/ably/route.js

What's more, we encode the claim in the token:

src/app/api/ably/route.js

Ably will include this claim in any events published by this client in topics with a name matching * . The * character is a wildcard, which Ably understands to mean the claim should be included in any event on any topic.

It's important to note that, after building up the token, we ultimately sign the token with ABLY_SECRET_KEY before returning it to the client.

Ably uses this same key to cryptographically verify the integrity of the token on the receiving end, ensuring the token was issued by a secure server (yours) and hasn't been tampered with. Coincidentally, this highlights just how important it is to keep your secret keys secret!

Next, update src/app/chat/[[...channelName]]/page.js to replace the hard-coded values with a link to the backend authUrl:

src/app/chat/[[...channelName]]/page.js

Finally, update src/app/[[...channel]]/chat/chat.js:

src/app/[[...channel]]/chat/chat.js

Reload the page and instead of the "AB" initials I hard-coded earlier, you should see your own!

Authenticated Next Chat App With Ably And Clerk guide illustration

Lock moderator-only channels

The moderator-only channel isn't accessible to unauthorized users, yet, the user interface makes it look clickable.

It would be a confusing experience if your user clicks the channel only to get an Ably error because they don't have the necessary capabilities based on their role.

Authenticated Next Chat App With Ably And Clerk guide illustration

In this section, let's quickly disable moderator-only channels and show a lock icon if the user isn't authorized to participate.

First, install the lucide-react icon library by running the following command:

terminal

This is where we'll get the lock icon from.

Next, update src/app/[[...channel]]/chat/channel-list.js:

src/app/[[...channel]]/chat/channel-list.js

Reload the page and you should see that the mods-only channel is "locked" and unclickable:

Authenticated Next Chat App With Ably And Clerk guide illustration

To really test if this works, we'll need to grant our user moderator permissions. If all is working well, the channel should become "unlocked" and accessible.

Managing moderator roles with Clerk

To promote your user to a moderator, open the Clerk Users tab in the dashboard and find your user.

If you've been making lots of test accounts like I sometimes do, a good tip is to sort by Last signed in:

Authenticated Next Chat App With Ably And Clerk guide illustration

Scroll to the bottom and Edit the public metadata to look like this:

Authenticated Next Chat App With Ably And Clerk guide illustration

Hit Save, then reload your app for good measure.

Your user and fellow moderators will now have access to the channel:

Authenticated Next Chat App With Ably And Clerk guide illustration

As an exercise, I like the idea of adding a /promote {user} command, similar to what you might find in Discord, say.

That would be totally doable on the backend using updateUserMetadata, but I'll leave it as a stretch goal for you should you like the sound of this challenge!

Implement message deleting

Next, let's enable users to delete messages.

Our requirements are as follows:

  • Any user should be able to delete their own message (we all make typos).
  • Meanwhile, moderators should have permission to delete anyone's messages.

Ably doesn't support deleting messages in the traditional sense, however, the service supports message interactions, which allow you to associate metadata such as "deleted" with a previously-sent message. This is known as a soft delete.

In the next section, we'll add a "delete message" button but, before implementing the code, I should explain how we plan to authorize message deletions. This will make the code in the next section a lot easier to understand.

At a high level, a message deletion is a special type of Ably event called a message interaction.

When the user click the "delete message" button, we'll publish an Ably message interaction called "delete" with an extras property that references the message they're trying to delete's identifier called a timeserial.

Here's a preview of code (you'll see where to slot it in the next section):

Publishes a delete Ably message interaction

Subscribers receive this event, at which point, we will execute some logic to process the message deletion:

Subscribes to a delete Ably message interaction
  • If the message identifier matches the deleted messages identifier, we move on to check if the user is allowed to delete it.
  • If the message-to-delete was sent by the same client that published the delete event, we know they're allowed to delete it so we remove it from the messages state.
  • Otherwise, they might be a moderator, so we check if the delete event has the isMod claim and, if so, proceed to remove the message from the messages state.

As a reminder, if the user is a moderator, Ably will include the isMod claim in any event they publish.

It's important to note that, while anyone can technically publish a delete message, we only process the deletion if the user owns the message or has the isMod claim. For anyone else who might publish a delete message, nothing happens, it has no effect.

With the theory out of the way (and a preview of the code), let's tie it all together in the next section.

Implement a delete message button

First, create the shadcn/ui menubar component by running the following command, we'll need it in a moment:

terminal

Next, enable message interactions for your Ably app.

To do this, open your app, click Settings, then Add new rule. Enter the namespace "chat", tick Message interactions enabled, then click Create channel rule.

Authenticated Next Chat App With Ably And Clerk guide illustration

Once you've done that, update src/app/[[...channel]]/chat/message-list.js:

src/app/[[...channel]]/chat/message-list.js

Finally, update src/app/[[...channel]]/chat/chat.js:

src/app/[[...channel]]/chat/chat.js

Reload the application. Assuming you're logged into the same account and still have isMod , you will have the ability to Delete any message.

Authenticated Next Chat App With Ably And Clerk guide illustration

Remove the isMod role from your user via the Clerk dashboard and reload the page for good measure, and the Delete button will now be greyed out:

Authenticated Next Chat App With Ably And Clerk guide illustration

Implement chat message history

Ably is not a database, but it does hold on to events history for 24-72 hours (free accounts are limited to 24 hours).

You should ideally store chat messages in your own databases for long-term processing (either using an Ably queue or by subscribing to events on your server), but Ably history allows us to conveniently populate the UI with recent messages so that users have some context about the conversation they're joining.

To enable message history, open your Ably app in the Ably dashboard, click Settings, spot the rule you created in the previous step for message interactions, click Edit, then tick Persist all messages. Finally, click Save.

Authenticated Next Chat App With Ably And Clerk guide illustration

Next, update src/app/[[...channel]]/chat/chat.js to fetch message history. While we're here, we'll also create a React hook that automatically scrolls messages into view:

src/app/[[...channel]]/chat/chat.js

Send a test message or two and reload the page. Whereas before, the chat start completely empty, the chat now loads with recent messages, allowing your users to find their place in the conversation.

Implement an online list

An online list creates a sense of togetherness among your users and subtly communicates who's online and likely to respond.

Authenticated Next Chat App With Ably And Clerk guide illustration

Using Ably, we can implement such functionality using a feature called presence.

Presence provides information and realtime updates about who's "present" on a particular topic.

First, create /src/app/chat[[...channel]]/whos-online-list.js:

/src/app/chat[[...channel]]/whos-online-list.js

Now, for the final time in this tutorial, update src/app/[[...channel]]/chat/page.js:

src/app/[[...channel]]/chat/page.js

As users come and go, so does the green light that indicates their online status.

Here, we use presence on the topic for the user's current chat channel, which means the online status is on a per-channel basis.

Alternatively, you could introduce a new topic like "clover-corp" (or whatever meaningful name you gave your app) and enable presence on a per-app basis instead. Remember, whenever you introduce a new Ably topic, you'll need to introduce another ChannelProvider.

Conclusion

In this tutorial, we fleshed out a highly-featured authenticated Next.js chat application, featuring message deletion, online presence, and a moderator role!

If you found this guide useful, tell us how you used it in your app on X, and be sure to tag @clerk to let us know!