| name | sse-payload-integration |
| description | Integrate SSE with Payload CMS for realtime updates. Covers afterChange hooks to trigger events, authenticated routes, and real-time social app features. |
| triggers | ["sse payload","sse cms","sse hooks","realtime cms","payload hooks","sse trigger"] |
SSE with Payload CMS
Trigger SSE notifications from CMS hooks for dynamic social feeds.
Architecture Overview
Payload CMS Hook → Event Emission → SSE Stream → Client Browser
(afterChange) (EventEmitter/Redis) (HTTP) (EventSource)
When user creates post, likes, or comments:
- Payload hook detects change
- Hook emits notification event
- SSE route broadcasts to connected clients
- Browser receives realtime update
Payload afterChange Hook
Add hook to collection to trigger notification:
import { CollectionConfig } from 'payload/types';
import { triggerNotification } from '@/app/api/notifications/route';
const Posts: CollectionConfig = {
slug: 'posts',
hooks: {
afterChange: [
async ({ doc, operation, previousDoc }) => {
if (operation === 'update') {
if (doc.likesCount > (previousDoc?.likesCount || 0)) {
await triggerNotification(doc.author.id, {
type: 'like',
message: `Your post got a like!`,
postId: doc.id,
});
}
if (doc.commentsCount > (previousDoc?.commentsCount || 0)) {
await triggerNotification(doc.author.id, {
type: 'comment',
message: `New comment on your post!`,
postId: doc.id,
});
}
}
return doc;
},
],
},
fields: [
],
};
export default Posts;
Payload Hook Patterns
Pattern 1: Notify Document Author
hooks: {
afterChange: [
async ({ doc }) => {
await triggerNotification(doc.author.id, {
type: 'document-update',
data: { docId: doc.id, title: doc.title },
});
},
],
}
Pattern 2: Notify All Followers
hooks: {
afterChange: [
async ({ doc, collection }) => {
const followers = await collection.db.find({
where: { following: { equals: doc.author.id } },
});
for (const follower of followers) {
await triggerNotification(follower.id, {
type: 'user-action',
data: { action: 'new-post', userId: doc.author.id },
});
}
},
],
}
Pattern 3: Aggregate Notifications
hooks: {
afterChange: [
async ({ doc }) => {
await aggregateNotification(doc.author.id, {
type: 'batch',
events: [{ postId: doc.id, action: 'created' }],
});
},
],
}
SSE Route with Auth
Authenticate users before streaming notifications:
import { verifyJwt } from '@/lib/auth';
import { getPayload } from 'payload';
export async function GET(request: NextRequest) {
const token = request.headers.get('authorization')?.split(' ')[1];
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const decoded = verifyJwt(token);
if (!decoded) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
const userId = decoded.userId;
}
Full Flow Example
1. User Creates Post
const response = await fetch('/api/collections/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'My new post', content: '...' }),
});
2. Payload Hook Triggers
hooks: {
afterChange: [
async ({ doc }) => {
await redis.publish(
`notifications:${follower.id}`,
JSON.stringify({
type: 'new-post',
post: { id: doc.id, title: doc.title, author: doc.author.name },
})
);
},
],
}
3. SSE Broadcasts to Clients
Connected EventSource clients receive notification in realtime.
4. React Component Updates
es.onmessage = (event) => {
const notification = JSON.parse(event.data);
};
Triggers Available in Payload Hooks
| Hook | When | Use For |
|---|
beforeChange | Before save (validate, transform) | Validate notification settings |
afterChange | After save (DB persisted) | Trigger notifications, trigger indexing |
beforeRead | Before fetch | Filter private notifications |
afterRead | After fetch | Enrich notification data |
Error Handling
Handle hook failures gracefully:
hooks: {
afterChange: [
async ({ doc }) => {
try {
await triggerNotification(doc.author.id, { type: 'update' });
} catch (error) {
console.error('Failed to trigger notification:', error);
}
},
],
}
Performance Considerations
Async Non-Blocking
Keep hooks fast by offloading work:
await triggerNotification(...);
triggerNotification(...).catch(err => console.error(err));
Batch Notifications
Avoid notifying 1000 followers individually:
for (const follower of followers) {
await redis.publish(`notifications:${follower.id}`, ...);
}
const pipeline = redis.pipeline();
for (const follower of followers) {
pipeline.publish(`notifications:${follower.id}`, ...);
}
await pipeline.exec();
AI Prompts
Prompt: "Generate a Payload afterChange hook that sends SSE notifications when a post gets liked."
Prompt: "Show me how to integrate Payload collection hooks with Redis pub/sub for realtime feeds."
Prompt: "Create an authenticated SSE endpoint that works with Payload JWT tokens."
Related Hooks
- beforeValidate: Validate incoming data
- beforeChange: Modify document before save
- afterChange: Post-save operations (notifications, indexing)
- afterDelete: Cleanup when document deleted