Use this guide to troubleshoot Server-Sent Events (SSE) issues.
Quick Path (80% of issues)
// turbo
-
Test SSE endpoint:
bash
1curl -N -H "Accept: text/event-stream" http://localhost:5000/api/notifications/stream
Should see: data: {"type":"Connected",...}
-
Check ProjectionCommitListener has case for your projection
-
Check QueryInvalidationService maps notification to query keys
-
Check ReactiveQuery uses matching queryKeys
If still broken, continue to full debugging below.
Symptoms
- ✗ Frontend doesn't update after mutation
- ✗
ReactiveQuery doesn't invalidate
- ✗ SSE connection fails or disconnects
- ✗ Events not received in browser
Prerequisites:
/aspire__start_solution - Solution must be running to debug SSE
First Steps:
/test__verify_feature - Run basic checks (build, tests) before deep debugging
Related Debugging:
/cache__debug_cache - If issue seems cache-related instead of SSE
/ops__doctor_check - Check if environment setup is correct
After Fixing:
/test__verify_feature - Confirm fix works
/test__integration_scaffold - Add tests to prevent regression
Debugging Steps
1. Verify SSE Endpoint is Working
Test the SSE endpoint directly:
bash
1# Terminal 1: Connect to SSE stream
2curl -N -H "Accept: text/event-stream" http://localhost:5000/api/notifications/stream
You should see:
: connected
data: {"type":"Connected","timestamp":"2026-01-15T20:00:00Z"}
If connection fails:
- ✗ Check API service is running
- ✗ Verify
/api/notifications/stream endpoint exists in NotificationEndpoints.cs
- ✗ Check firewall/network issues
2. Verify Notifications Are Defined
Check src/Shared/BookStore.Shared/Notifications/DomainEventNotifications.cs:
csharp
1// ✅ Correct - implements IDomainEventNotification
2public record BookCreatedNotification(Guid Id, string Title) : IDomainEventNotification;
3
4// ✗ Wrong - missing interface
5public record BookCreatedNotification(Guid Id, string Title);
If notification is missing:
- Create notification in
DomainEventNotifications.cs
- Implement
IDomainEventNotification interface
- Include all data needed for frontend invalidation
3. Verify ProjectionCommitListener Configuration
Open src/BookStore.ApiService/Infrastructure/MartenCommitListener.cs:
Check if your projection has a handler:
csharp
1private async Task ProcessDocumentChangeAsync(
2 IDocumentChange change,
3 CancellationToken cancellationToken)
4{
5 switch (change)
6 {
7 case BookProjection proj:
8 await HandleBookChangeAsync(proj, cancellationToken);
9 break;
10
11 // ❌ Missing: Your projection case
12 case AuthorProjection proj:
13 await HandleAuthorChangeAsync(proj, cancellationToken);
14 break;
15 }
16}
Check if handler sends notification:
csharp
1private async Task HandleBookChangeAsync(
2 BookProjection book,
3 CancellationToken cancellationToken)
4{
5 var notification = new BookUpdatedNotification(book.Id, book.Title);
6
7 // ✅ Correct - sends notification
8 await _notificationService.NotifyAsync(notification, cancellationToken);
9
10 // ✗ Wrong - forgot to send
11 // (no NotifyAsync call)
12}
If handler is missing:
- Add case for your projection
- Create handler method that calls
NotifyAsync
- Use appropriate notification type
4. Verify QueryInvalidationService Mapping
Open src/Web/BookStore.Web/Services/QueryInvalidationService.cs:
Check if notification maps to query keys:
csharp
1IEnumerable<string> GetInvalidationKeys(IDomainEventNotification notification)
2{
3 switch (notification)
4 {
5 case BookCreatedNotification n:
6 yield return "Books";
7 yield return $"Book:{n.EntityId}";
8 break;
9 case BookUpdatedNotification n:
10 yield return "Books";
11 yield return $"Book:{n.EntityId}";
12 break;
13
14 // ❌ Missing: Your notification
15 case AuthorUpdatedNotification n:
16 yield return "Authors";
17 yield return $"Author:{n.EntityId}";
18 break;
19 }
20}
If mapping is missing:
- Add case for your notification type
- Yield return query keys that should be invalidated
- Match keys used in
ReactiveQuery setup
5. Verify Frontend BookStoreEventsService
Check browser console for SSE connection:
In Chrome DevTools:
- Open Network tab
- Look for "events" request (type: eventsource)
- Check status is "pending" (active connection)
- View "EventStream" tab to see events
If connection is closed:
- Check
BookStoreEventsService.StartListening() is called in OnInitializedAsync
- Verify base URL is correct
- Check for JavaScript errors
6. Verify ReactiveQuery Configuration
Check component using ReactiveQuery:
csharp
1// ✅ Correct - query keys match invalidation mapping
2bookQuery = new ReactiveQuery<PagedListDto<BookDto>>(
3 queryFn: FetchBooksAsync,
4 eventsService: BookStoreEventsService,
5 invalidationService: InvalidationService,
6 queryKeys: new[] { "Books" }, // Matches QueryInvalidationService
7 onStateChanged: StateHasChanged,
8 logger: Logger
9);
10
11// ✗ Wrong - query keys don't match
12queryKeys: new[] { "AllBooks" } // Doesn't match "Books"
If query doesn't invalidate:
- Ensure
queryKeys match QueryInvalidationService mapping
- Verify
BookStoreEventsService is subscribed
- Check
onStateChanged callback is provided
7. Test End-to-End
Perform a mutation and watch the flow:
bash
1# Terminal 1: Watch SSE stream
2curl -N -H "Accept: text/event-stream" http://localhost:5000/api/notifications/stream
3
4# Terminal 2: Trigger mutation
5curl -X POST http://localhost:5000/api/admin/books \
6 -H "Content-Type: application/json" \
7 -d '{"title":"Test Book",...}'
Expected flow:
- Command executed
- Event stored in Marten
ProjectionCommitListener triggered
- Notification sent via SSE
- Browser receives event
QueryInvalidationService maps to keys
ReactiveQuery invalidates
- Query refetches
- UI updates
If any step fails, locate where:
- Check logs in Aspire dashboard
- Add debug logging to
ProjectionCommitListener
- Use browser console to see received events
Common Issues & Fixes
Issue: Events Not Sent
Symptom: ProjectionCommitListener not triggered
Fix:
- Ensure
ProjectionCommitListener is registered in DI
- Check Marten event store configuration
- Verify projection lifecycle (
Inline vs Async)
Issue: Wrong Event Type
Symptom: Notification sent but frontend doesn't invalidate
Fix:
csharp
1// Check notification type name matches exactly
2case "BookUpdatedNotification": // ✅ Correct
3case "BookUpdated": // ✗ Wrong
Issue: Multiple Tabs Don't Update
Symptom: Updates only visible in tab that made change
Fix:
- SSE works per-connection, each tab needs own connection
- Each tab should call
BookStoreEventsService.StartListening()
- Verify SignalR isn't being used (project uses SSE)
Issue: SSE Connection Drops
Symptom: Connection works then stops
Fix:
- Check server-side timeout configuration
- Verify no proxy/load balancer kills long connections
- Add reconnection logic in
BookStoreEventsService
Verification Checklist
Backend:
- Aspire Dashboard → Structured Logs → Filter by "notification"
- Add logging in
ProjectionCommitListener: _logger.LogInformation("Sending {Type}", notification.GetType().Name)
Frontend:
- Browser Console → Look for EventSource logs
- React DevTools → Check component re-renders
- Network tab → Verify EventSource connection
First Steps:
/test__verify_feature - Run basic checks (build, tests) before deep debugging
Related Debugging:
/cache__debug_cache - If issue seems cache-related instead of SSE
/ops__doctor_check - Check if environment setup is correct
After Fixing:
/test__verify_feature - Confirm fix works
/test__integration_scaffold - Add tests to prevent regression
See Also: