Back to Blog
tutorialJanuary 25, 202610 min read

Build Real-Time Notifications in Next.js with Redis Pub/Sub

There's something magical about real-time notifications. A message appears, a badge updates, a toast pops up—and it all happens instantly. No refresh needed.

Most tutorials will tell you to set up WebSockets, configure Socket.io, manage connection state... it gets complicated fast. But there's a simpler way using Redis Pub/Sub and Server-Sent Events.

The Architecture

Here's the beautiful simplicity of this approach:

  • Redis Pub/Sub — Routes messages between servers
  • Server-Sent Events — Pushes to browsers (one-way, simple)
  • Next.js API Routes — Handles the SSE connections

No WebSocket servers to manage. No sticky sessions. It just works.

Publishing Notifications

When something happens that a user should know about:

import Redis from 'ioredis'
const redis = new Redis(process.env.REDIS_URL)

async function notify(userId, message) {
  await redis.publish(
    `notifications:${userId}`,
    JSON.stringify({
      id: crypto.randomUUID(),
      ...message,
      timestamp: Date.now()
    })
  )
}

That's it for the sender side. One line to publish.

The SSE Endpoint

This is where the magic happens:

// app/api/notifications/route.ts
export async function GET(req) {
  const userId = await getUserId(req)
  
  const stream = new ReadableStream({
    start(controller) {
      const sub = new Redis(process.env.REDIS_URL)
      sub.subscribe(`notifications:${userId}`)
      
      sub.on('message', (ch, msg) => {
        controller.enqueue(`data: ${msg}\n\n`)
      })
    }
  })
  
  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache'
    }
  })
}

Client-Side Hook

function useNotifications() {
  const [items, setItems] = useState([])
  
  useEffect(() => {
    const es = new EventSource('/api/notifications')
    
    es.onmessage = (e) => {
      const notif = JSON.parse(e.data)
      setItems(prev => [notif, ...prev])
      toast(notif.title) // Show toast
    }
    
    return () => es.close()
  }, [])
  
  return items
}

Why this works so well

SSE connections are just HTTP. They work through proxies, load balancers, and CDNs without any special configuration. And browsers handle reconnection automatically.

Real-World Usage

Now you can send notifications from anywhere in your app:

// When an order ships
await notify(order.userId, {
  title: 'Order Shipped! 📦',
  body: 'Your package is on its way'
})

// When someone comments
await notify(post.authorId, {
  title: 'New Comment',
  body: `${commenter.name} commented on your post`
})

Real-time notifications transform how users experience your app. And with Redis Pub/Sub, they're surprisingly easy to implement.

Ready to build something amazing?

Get your Redis database running in 30 seconds. No credit card required.

Start Free →