← Back to Notes

Realtime messaging app with NextJS, Clerk, and Stream

Hamed Bahram /
4 min read--- views

In this tutorial, we'll walk you through the process of building a real-time messaging app using three powerful technologies: NextJS, Clerk, and Stream. By the end of this guide, you'll have a fully functional chat application and the knowledge to customize it further.

Source code

Enter your Github username and email to access the source code.

What You'll Learn

Our tutorial covers the creation of a modern messaging application with a focus on server-side operations. You'll learn how to:

  1. Set up a Next.js project using the new App Router
  2. Implement secure user authentication with Clerk
  3. Create a real-time chat experience using Stream's APIs and SDKs
  4. Utilize server actions for dynamic token generation and channel creation

Tech Stack Overview

The tutorial leverages a powerful combination of technologies:

  • NextJs: A React framework for building server-side rendered and static web applications, with a focus on the new App Router
  • Clerk: A complete user management and authentication solution
  • Stream: Powerful APIs and SDKs for building scalable and feature-rich chat experiences

Key Features of the Messaging App

  1. User Authentication: Secure sign-up and login functionality using Clerk
  2. Real-Time Messaging: Instant message delivery and updates powered by Stream
  3. Dynamic Token Generation: Server-side authentication with Stream using NextJs server actions
  4. Customized Channel List: Enhanced channel management with the ability to create new conversations

Let's dive into some code examples to see how these features are implemented.

Deep Dive: Server-Side Innovation with NextJs App Router

1. NextJs App Router

We leverage the new App Router in Next.js, which provides improved routing capabilities and enhanced server-side rendering. Here's a basic example of how you might structure your app:

// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'
 
export default function RootLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}
 
// app/page.tsx
import { auth } from '@clerk/nextjs'
import StreamChat from '@/components/StreamChat'
 
export default async function Home() {
  const { userId } = auth()
 
  if (!userId) {
    return <div>Please sign in to access the chat.</div>
  }
 
  return <StreamChat userData={{ id: userId }} />
}

2. Server Actions for Stream Authentication

One of the highlights of our project is the use of server actions to dynamically generate authentication tokens. Here's an example of how this might be implemented:

// lib/actions.ts
'use server'
 
import { StreamChat } from 'stream-chat'
 
export async function createToken(userId: string) {
  const serverClient = StreamChat.getInstance(
    process.env.STREAM_API_KEY!,
    process.env.STREAM_API_SECRET
  )
  const token = serverClient.createToken(userId)
  return token
}

In our StreamChat component, we use this server action like this:

const tokenProvider = useCallback(async () => {
  return await createToken(userData.id)
}, [userData.id, createToken])

3. Customized Channel List

We've extended Stream's functionality by customizing the channel list. Here's a snippet from our StreamChat component showing how we implement the custom list:

<ChannelList
  sort={sort}
  filters={filters}
  options={options}
  List={CustomListContainer}
  sendChannelsToList
/>

The StreamChat Component

Here's the full StreamChat component, which ties everything together:

'use client'
 
import Link from 'next/link'
import { cn } from '@/lib/utils'
import { useCallback } from 'react'
import { useTheme } from 'next-themes'
import { createToken } from '@/lib/actions'
 
import {
  Chat,
  Channel,
  ChannelHeader,
  ChannelList,
  MessageInput,
  MessageList,
  Thread,
  Window,
  useCreateChatClient,
  DefaultStreamChatGenerics
} from 'stream-chat-react'
 
// ... (rest of the imports)
 
export default function StreamChat({ userData }: StreamChatProps) {
  const { resolvedTheme } = useTheme()
 
  const tokenProvider = useCallback(async () => {
    return await createToken(userData.id)
  }, [userData.id, createToken])
 
  const client = useCreateChatClient({
    userData,
    tokenOrProvider: tokenProvider,
    apiKey: process.env.NEXT_PUBLIC_STREAM_API_KEY!
  })
 
  // ... (rest of the component logic)
 
  return (
    <Chat
      client={client}
      theme={cn(
        resolvedTheme === 'dark'
          ? 'str-chat__theme-dark'
          : 'str-chat__theme-light'
      )}
    >
      {/* Sidebar navigation */}
      <aside className="inset-y z-20 flex h-full flex-col border-r">
        {/* ... (navigation buttons) */}
      </aside>
 
      <ChannelList
        sort={sort}
        filters={filters}
        options={options}
        List={CustomListContainer}
        sendChannelsToList
      />
 
      <Channel EmojiPicker={EmojiPicker} emojiSearchIndex={SearchIndex}>
        <Window>
          <ChannelHeader />
          <MessageList />
          <MessageInput audioRecordingEnabled />
        </Window>
 
        <Thread />
      </Channel>
    </Chat>
  )
}

This component showcases:

  • Integration with Stream's chat SDK
  • Use of server actions for authentication
  • Customization of the channel list
  • Theming support
  • A full-featured chat interface with channels, message lists, and threads

Recap

This video tutorial offers a comprehensive guide to building a modern, scalable messaging application with advanced server-side capabilities. The combination of Next.js App Router, Clerk, Stream, and innovative use of server actions creates a powerful system that prioritizes security, performance, and user experience.

Whether you're building a messaging app for a small team or a large community, the techniques and technologies covered in this tutorial will provide you with the tools you need to create a feature-rich, secure, and highly customizable platform.