⚡ Next.js 심화 14장: Real-time Strategies — WS와 SSE, 서버리스와 실시간의 동행
📋 개요
WebSocket과 SSE를 Next.js에서 구현하는 방법과 서버리스 환경의 실시간 통신 전략입니다.
📋 목차
- 📌 이 문서를 읽기 전에
- 🤔 왜 알아야 하는가
- 🏗️ 비유로 먼저 이해하기
- 🗺️ WebSockets vs SSE vs Polling 🟢
- 🏗️ Next.js + 외부 WS 서버 (Spring) 연동 아키텍처 🟡
- 🛡️ Route Handler에서 SSE(Server-Sent Events) 구현하기 🟡
- 🚀 서버리스 환경의 실시간 한계와 외부 서비스(Pusher, Ably) 🔴
- 🏁 이번에 배운 내용 총정리
- 📝 마무리 퀴즈
- 🔗 더 알아보기
📌 이 문서를 읽기 전에
⏱️ 예상 읽기 시간: 22분 (전체) / 핵심 파트만: 11분
🗺️ 이 문서의 배경 세계관: '영수네 커뮤니티'
- 영숙(디자이너): "영수 님, 우리 커뮤니티 채팅창요. 메시지가 오면 유저가 매번 새로고침을 눌러야 확인되는 건 너무 90년대 스타일 아니에요?"
- 영수(PM): "듣고 보니 그렇네요! 영철 님, 우리도 배달의민족처럼 메시지가 실시간으로 '뿅' 하고 나타나게 해주세요."
- 영철(주니어): "문제없죠! Next.js 안에 바로
socket.io서버를 구축해 보겠습니다!" - 영호(리드): "영철 님... 스톱! Next.js(Vercel)는 서버리스 환경이에요. 서버가 항상 켜져 있는 게 아니라 요청이 올 때만 잠깐 깨어나요. 상시 연결이 필요한 WebSocket 서버를 Next.js 안에 직접 넣는 건 매우 위험한 생각입니다."
🎯 이 문서를 다 읽으면 할 수 있는 것
- 실시간 데이터 성격에 따라 WebSockets(WS)과 Server-Sent Events(SSE) 중 무엇이 적합한지 판단할 수 있다
- Next.js 외부(Spring 등)의 WebSocket 서버와 안전하게 통신하는 구조를 설계할 수 있다
- Route Handler를 이용해 별도 서버 없이도 단방향 실시간 알림(SSE)을 구현할 수 있다
🤔 왜 알아야 하는가
Next.js는 기본적으로 요청-응답(Request-Response) 모델에 최적화된 프레임워크야.
하지만 현대적인 앱은 실시간 채팅, 주식 뉴스, 라이브 알림이 필수지.
핵심은 서버리스와의 충돌을 이해하는 거야:
- WebSockets: 항시 연결을 유지해야 함. 서버리스(Lambda 등)는 30초~1분 후 꺼지려고 함.
- 해결책: 상시 켜져 있는 외부 서버(Node.js, Spring)를 쓰거나, 서버리스용 실시간 서비스(Pusher 등)을 써야 해.
🏗️ 비유로 먼저 이해하기
🛵 음식 배달로 설명한다면?
- Short Polling: 5분마다 배달 앱을 열어서 "치킨 왔어요? 왔어요?" 하고 묻는 것. (서버/클라이언트 모두 피곤)
- Server-Sent Events (SSE): 가게 주인이 전화를 끊지 않고 들고 있다가, 치킨이 나오면 "나왔어요!" 하고 소리치는 것. (한쪽만 말함)
- WebSockets (WS): 손님과 가게 주인이 무전기를 들고 있는 것. 실시간으로 서로 수다 떨 수 있음. (양방향 연결)
🗺️ WebSockets vs SSE vs Polling 🟢
| 방식 | 통신 방향 | 특징 | 적합한 경우 |
|---|---|---|---|
| Short Polling | 양측 가능 | 가장 간단하지만 리소스 낭비 심함 | 성능이 전혀 중요하지 않은 관리자 페이지 |
| SSE | 단방향 (S -> C) | HTTP 기반이라 가볍고 자동 재연결 지원 | 주식 시세, SNS 타임라인, 간단한 알림 |
| WebSockets | 양방향 | TCP 기반 상시 연결, 가장 빠름 | 실시간 채팅, 멀티플레이어 게임 |
🏗️ Next.js + 외부 WS 서버 (Spring) 연동 아키텍처 🟡
가장 추천하는 실무 구조야: 인증은 Next.js가, 통신은 Spring WebSocket 서버가!
// hooks/useChat.ts (Client Component 전용)
'use client'
import { useEffect, useState } from 'react'
export function useChat(roomId: string) {
const [messages, setMessages] = useState<string[]>([])
const [socket, setSocket] = useState<WebSocket | null>(null)
useEffect(() => {
// ✅ Next.js 밖의 Spring 서버 주소
const ws = new WebSocket(`wss://api.youngsu.com/chat/${roomId}`)
ws.onmessage = (event) => {
setMessages((prev) => [...prev, event.data])
}
setSocket(ws)
// ✅ 컴포넌트 언마운트 시 연결 해제 (중요!)
return () => ws.close()
}, [roomId])
const sendMessage = (msg: string) => {
socket?.send(msg)
}
return { messages, sendMessage }
}🛡️ Route Handler에서 SSE(Server-Sent Events) 구현하기 🟡
별도 서버 없이 Next.js 앱 하나로 실시간 알림을 보내고 싶다면 SSE가 정답이야.
// app/api/notifications/route.ts
export async function GET() {
const encoder = new TextEncoder()
// ✅ ReadableStream을 이용한 실시간 응답
const stream = new ReadableStream({
async start(controller) {
// 10초마다 날씨 알림을 보내는 시나리오
const interval = setInterval(() => {
const data = encoder.encode(`data: ${JSON.stringify({ msg: '비가 올 예정이에요!' })}\n\n`)
controller.enqueue(data)
}, 10000)
// 연결 종료 시 정리
return () => clearInterval(interval)
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
})
}🚀 서버리스 환경의 실시간 한계와 외부 서비스 🔴
만약 너의 Next.js 앱이 Vercel이나 AWS Lambda에 배포된다면, 위의 SSE 코드도 완벽하지 않아. 람다 실행 시간이 끝나면 연결이 끊기거든.
시니어의 선택 도구:
- Pusher: 서버리스에서도 동작하는 WebSocket 서비스. 가장 대중적.
- Upstash Redis (Pub/Sub): 서버리스 최적화 Redis. 앱 인스턴스 간 이벤트 전달용.
- AWS AppSync: 대규모 실시간 서비스가 필요할 때 끝판왕.
🏁 이번에 배운 내용 총정리
📋 실시간 전략 선택 가이드
| 요구사항 | 최선의 선택 | 이유 |
|---|---|---|
| 매우 빈번한 양방향 대화 | WebSockets (External Server) | 오버헤드가 가장 낮고 빠름 |
| 뉴스 알림, 단방향 피드 | SSE (Route Handler) | HTTP 표준이며 자동 재연결이 내장됨 |
| 서버리스 배포 환경 | Pusher / Ably / Socket.io on ECS | 인프라 관리가 필요 없고 항시 연결 보장 |
⚠️ 절대 하지 말 것
| 상황 | ❌ 나쁜 예 | ✅ 좋은 예 |
|---|---|---|
| Next.js 내장 | app/api/ws/route.ts에 소켓 서버 구현 | 별도 Node.js/Spring 서버로 WS 분리 |
| 연결 관리 | useEffect 청소(Clean-up) 함수 생략 | ws.close() 호출로 메모리 누수 방지 |
| 인증 | WebSocket URL에 토큰 노출 | 1회용 티켓(One-time Ticket)이나 쿠키 인증 사용 |
📝 마무리 퀴즈
Q1. Vercel 같은 서버리스 환경에 일반 WebSocket 서버를 Next.js 앱 내부에 오래 붙들기 어려운 이유는 무엇인가?
✅ 정답: 서버리스 함수는 요청 단위로 실행되는 무상태 모델이라 장시간 유지되는 양방향 연결을 안정적으로 붙들기 어렵기 때문이다.
💡 상세 해설: WebSocket은 연결을 계속 유지하는 stateful 서버가 필요하다. 서버리스 함수는 짧은 요청 처리에 최적화되어 있으므로, 채팅이나 협업처럼 장기 연결이 필요한 기능은 Spring/Node 상시 서버나 Ably, Pusher, Supabase 같은 외부 실시간 서비스를 검토해야 한다.
Q2. SSE가 WebSocket보다 어울리는 상황은 언제인가?
✅ 정답: 서버에서 클라이언트로 이벤트를 계속 밀어주면 충분하고, 클라이언트가 같은 연결로 복잡한 양방향 메시지를 보낼 필요가 없을 때다.
💡 상세 해설: SSE는 일반 HTTP 기반이고 브라우저 EventSource가 자동 재연결을 지원한다. 알림, 진행률, 운영 공지, 읽기 전용 피드처럼 단방향 스트림이면 구현과 운영이 단순하다. 반대로 채팅 입력, 게임, 공동 편집은 WebSocket 계열이 더 자연스럽다.
Q3. 영철이의 테스트 타임: 영수네 커뮤니티에 "새 댓글 알림"과 "1:1 채팅"이 동시에 필요하다. 같은 기술로 밀어붙여도 될까?
✅ 정답: 요구를 나눠야 한다. 새 댓글 알림은 SSE나 푸시 기반 서비스로 충분할 수 있고, 1:1 채팅은 WebSocket 또는 외부 realtime 플랫폼이 더 적합하다.
💡 상세 해설: 실시간이라는 단어만 같아도 방향성, 지연 허용치, 연결 수, 인증 갱신, 배포 환경이 다르다. 영호라면 먼저 단방향/양방향, 유실 허용 여부, 서버리스 제약, 장애 시 재연결 전략을 표로 비교하라고 할 것이다.
🐣 영철이의 퇴근 일기
오늘은 실시간 기능을 "WebSocket을 붙이면 끝"이라고 말하지 않게 됐다. 알림, 채팅, 진행률, 협업 편집은 모두 실시간처럼 보이지만 필요한 연결 방식과 운영 비용이 다르다.
💡 "실시간 전략은 기술 이름이 아니라 메시지 방향, 연결 수명, 배포 환경의 조합으로 고른다."
다음 기획 회의에서 실시간 요구가 나오면 먼저 질문부터 하겠다. 사용자가 답장을 보내야 하는지, 몇 초 지연을 허용하는지, 서버리스에 올릴지, 끊겼을 때 어디서부터 다시 받을지. 이제는 영호에게 "WS로 하면 되죠?"가 아니라 선택지를 비교한 표를 가져갈 수 있을 것 같다.