TL;DR
서버에서 보낸 이벤트 (SSE)는 평범한 HTTP를 기반으로 한 서버 → 브라우저 방향의 단일 스트리밍 프로토콜입니다. 브라우저는 new EventSource(url)과 연결을 열고, 서버는 연결을 유지하며 원하는 대로 data: 줄을 푸시합니다. 그것이 전부입니다. 웹소켓 핸드셰이크 없이, 없이ws:// 업그레이드는 없습니다. 자동으로 재연결되고, HTTP를 사용하는 모든 프록시를 통해 작동하며, 2012년 이후 모든 브라우저에 포함되어 있습니다.
서버 → 클라이언트 스트리밍이 필요하고 양방향 메시징이 필요하지 않다면, SSE는 2026년에 중요한 모든 측면에서 WebSockets보다 우수합니다 : 코드가 적고 인프라가 간단하며 자동 재연결, 자동 이벤트 ID를 위한 재개 가능성. WebSocket을 사용할 유일한 이유는 양방향 채팅 스타일의 트래픽이나 이진 프레임입니다.
지금 바로 스트리밍을 보고 싶으신가요? 무료 SSE 테스터를 엽니다. SSE 엔드포인트를 붙여넣고 실시간으로 이벤트를 확인하세요.
30초 심리 모델
[ Browser ] ──── GET /events ───► [ Server ]
◄── HTTP 200, keep alive
◄── data: hello\n\n
◄── data: world\n\n
◄── data: ...
그것은 닫히지 않는 오래 지속되는 HTTP GET 요청으로, 응답 본문에는 특정 텍스트 형식이 있습니다. MIME 유형은 text/event-stream입니다. 새 줄바꿈마다 이벤트를 onmessage으로 플러시합니다. 브라우저는 프레임링, 파싱, 재연결을 처리합니다 — 당신은 eventSource.onmessage = ...을 작성하면 끝납니다.
가장 작은 예제
서버 (Node.js — 라이브러리가 필요 없음):
import http from 'node:http';
http.createServer((req, res) => {
if (req.url !== '/events') {
res.writeHead(404).end();
return;
}
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
let n = 0;
const interval = setInterval(() => {
res.write(`data: tick ${++n}\n\n`);
}, 1000);
req.on('close', () => clearInterval(interval));
}).listen(3000);
클라이언트 (어디서든 — <script> 태그 포함):
const es = new EventSource('/events');
es.onmessage = (e) => console.log('got:', e.data);
es.onerror = () => console.warn('disconnected, browser will retry');
서버를 실행하고, 클라이언트를 브라우저 콘솔에 붙여넣으면 20줄 미만의 코드로 스트리밍을 할 수 있습니다. 빌드 단계가 없습니다. 라이브러리도 없습니다. 업그레이드 협상도 없습니다. 그것이 전체 캠페인입니다.
2026년에 SSE가 얼마나 낮게 평가받는지에 대해
"실시간"이라고 들으면 팀들이 웹소켓을 본능적으로 사용하려고 하고, 그런 다음 한 주 동안 프록시 타임아웃, 스티키 세션, ALB 업그레이드 헤더를 디버깅한다. 사용 사례의 반은 서버 → 클라이언트만이었는데 — 피드, 알림 다이어로그, 빌드 로그, 실시간 카운터 같은 것이었다. 그것은 SSE 영역이다.
SSE를 사용하면 웹소켓으로 직접 구축해야 할 것들을 무료로 얻을 수 있는 것은 다음과 같다.
1. 자동 재연결. 연결이 끊어지면 브라우저는 ~3초간 기다렸다가 다시 연결합니다. 이에 대한 코드를 작성하지 않습니다. WebSockets를 사용하면 경력 전체에 걸쳐 각 프로젝트에서 동일한 백오프 루프를 작성해야 합니다.
2. 이벤트 ID와 재개. 서버가 id: 42\n을 보내면 브라우저는 다음 재연결 시 헤더로 Last-Event-ID: 42을 보냅니다. 서버는 그곳에서 다시 시작할 수 있습니다. 이것은SSE가 빌드 로그, AI 스트리밍, 감사 피드에 탁월한 특징인 기능. WebSockets를 사용하면 이것은 당신이 직접 설계한 커스텀 프로토콜입니다.
3. 표준 HTTP 전역 사용. SSE는 GET와 Accept: text/event-stream를 가지고 있습니다. 모든 프록시, 모든 CDN, 모든 WAF, 모든 역프록시가 이를 이해합니다 (하나의 예외는 아래 참조). 쿠키,Authorization는 CORS, 압축 모두 정상적으로 작동합니다. WebSockets는 업그레이드 춤이 필요하며 프록시는 종종 이를 잘못 처리합니다.
4. 프레임 없음. 텍스트만, 줄 제한됨. 당신은 curl SSE 엔드포인트를 만들고 터미널에서 스트림을 읽을 수 있습니다. WebSockets로는 그것을 시도해 보세요.
curl -N -H "Accept: text/event-stream" https://api.example.com/events
SSE 프레임의 구조
형식은 매우 간단하지만 사람들이 혼란스러워합니다. 각 이벤트는 하나 이상의 줄로 구성되며, 두 개의 새 줄로 끝납니다(공백 줄):
event: user-joined
id: 423
retry: 5000
data: {"userId":42,"name":"Ada"}
data: simple message
data: continuation of the same event
-
data:— 페이로드. 동일한 이벤트 내의 여러data:줄은\n로 결합됩니다. -
event:— 이벤트를 설정합니다이름을 듣고,es.addEventListener('user-joined', ...)을 사용합니다. 생략 시onmessage이 발생합니다. -
id:— 브라우저가 재연결 시Last-Event-ID으로 보내는 내용입니다. -
retry:— 재연결을 기다리기 전에 기다리는 밀리초입니다.
공백 줄은 필수입니다을 잊으면 안 됩니다.\n는 #1 SSE 버그입니다. 프록시와 클라이언트에서 이벤트 버퍼를 볼 수 없습니다.
SSE 대비 WebSockets 대비 Long Polling을 사용할 때
이것이 필요한 유일한 결정 트리입니다.
SSE를 사용할 때:
- 트래픽이 서버 → 클라이언트만입니다 (알림, 피드, 대시보드, 로그, AI 토큰 스트리밍)
- 자동 재연결을 원하지만 작성하지 않으려는 경우
- 텍스트을 스트리밍하고 있습니다 (JSON, 마크다운 조각, 로그 라인)
- 디버깅을 원하시면
curl
웹소켓을 사용할 때:
- 양방향이고 고주파인 트래픽이 있을 때 (채팅, 공동 편집, 멀티플레이 게임)
- バイ너리 프레임이 필요할 때 (오디오/비디오, 사용자 지정 프로토콜)
- 클라이언트 → 서버 메시지에 대해 100밀리초 미만의 라운드 트립이 필요할 때
긴 폴링을 사용할 때:
- 당신이 두 가지 모두를 깨뜨리는 인프라에 갇혀 있을 때 (2026년에는 드물지만 적극적인 기업 프록시 뒤에서 발생할 수 있습니다)
양쪽 모두에 코드가 포함된 전면적인 문제 해결은 SSE 대비 WebSockets 대비 긴 폴링 심층 분석.
세 가지 생산성 함정
이는 SSE를 처음 배포할 때 각 팀을 공격하는 버그입니다.
1. 프록시 버퍼가 당신을 죽입니다
Nginx는 기본적으로 응답을 버퍼링합니다. 당신의 res.write가 버퍼에 도착하면 클라이언트는 몇 분 동안 아무것도 볼 수 없습니다. 두 가지 해결책이 있으며, 둘 다 해결하세요:
서버 응답 헤더:
X-Accel-Buffering: no
Nginx 구성:
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 24h;
Cloudflare는 버퍼가 너무 많습니다 — SSE 엔드포인트를 캐시되지 않은 경로에 배치하거나 Cloudflare를 인지하는 스트리밍 설정을 사용해야 합니다.
2. 브라우저의 6개 연결당 원래 제한
브라우저는 약 6개의 열린 HTTP/1.1 연결을 원래에 제한합니다. 만약 앱이 열면EventSource와 사용자가 5개 더 탭을 열면 7번째 탭이 연결 슬롯이 없어 멈춥니다. 두 가지 해결책:
- HTTP/2 또는 HTTP/3 사용 — 연결 멀티플렉싱은 하나의 TCP 연결에 수백 개의 스트림을 의미합니다. 이것이 현대적인 해결책입니다.
-
탭 간에
BroadcastChannel또는SharedWorker를 통해 조정합니다 — 하나의 탭이 EventSource를 유지하며 이벤트를 자매 탭으로 브로드캐스팅합니다.
3. 심박수와 대기 시간 초과
웹소켓과 동일한 함정: 로드 밸런서는 대기 중인 연결을 닫습니다. AWS ALB는 기본적으로 50초입니다. 15-30초마다 한 줄의 주석 줄을 전송하세요:
setInterval(() => res.write(':keepalive\n\n'), 15000);
:로 시작하는 줄은 사양에 따른 주석입니다 — 이들은 연결을 따뜻하게 유지하면서 발동하지 않습니다onmessage on the client.
인증 및 SSE
EventSource는 하나의 귀찮은 제한 사항이 있습니다: 커스텀 헤더를 를 구성할 때 설정할 수 없습니다.Authorization: Bearer .... 선택지:
1. 쿠키(브라우저 앱용으로 권장됨) — 브라우저가 자동으로 전송합니다.withCredentials: true와 CORS-허용을 원본으로 설정하세요.
const es = new EventSource('/events', { withCredentials: true });
2. 쿼리 문자열 토큰 — 작동하지만 토큰이 서버 로그에 남음.
3. fetch + ReadableStream 패턴 — EventSource을 fetch에서 제거하며, 는 커스텀 헤더를 허용합니다. 자동 재연결을 잃고 본인이 형식을 파싱해야 하지만, 토큰 인증을 사용하는 현대 앱에 적합한 결정입니다:
const res = await fetch('/events', {
headers: { Authorization: `Bearer ${token}` },
});
const reader = res.body.getReader();
// ... parse text/event-stream chunks manually
@microsoft/fetch-event-source와 같은 라이브러리는 이를 대신해주고, 대부분의 현대 AI 스트리밍 클라이언트가 밑단에서 사용하는 것입니다.
SSE는 AI 스트리밍을 가능하게 하는 것입니다
지난 두 년 동안 ChatGPT, Claude, 또는 어떤 AI 채팅 UI를 사용했다면 SSE의 작동을 본 적이 있을 거예요. 서버는 토큰이 생성될 때마다 스트리밍합니다:
data: {"choices":[{"delta":{"content":"Hello"}}]}
data: {"choices":[{"delta":{"content":" world"}}]}
data: [DONE]
Anthropic의, OpenAI의, 그리고 Google의 스트리밍 API는 모두 SSE (또는 거의 동일한 커스텀 이벤트 스트림 형식)를 사용합니다. SSE에 대해 알면 다른 웹 프로토콜과 동일한 도구를 사용하여 AI 스트리밍 엔드포인트를 디버깅할 수 있습니다.
SSE 엔드포인트를 테스트하는 방법
각각을 사용할 때 순서대로 세 가지 방법입니다.
1. curl -N — 버퍼 없음 플래그. 가장 빠른 건강 검사입니다.
curl -N -H "Accept: text/event-stream" https://api.example.com/events
2. 브라우저 내 SSE 테스터 — URL을 붙여넣고 실시간 이벤트를 확인하고 헤더를 확인하고 재연결 동작을 확인하세요.여기서 무료로 하나 받으세요 설치 없이.
3. 브라우저 개발 도구 → 네트워크 → 이벤트 스트림 탭 — 앱이 열리면 실시간으로 파싱된 이벤트 스트림을 볼 수 있습니다. 크롬 기반 브라우저에는 이를 위한 전용 탭이 있습니다.
일반적인 오해
- "SSE는 죽었고, WebSockets가 그것을 대체했습니다." 웹소켓은 성장했지만 SSE는 결코 어디에도 가지 않았다 — AI 스트리밍, GitHub Actions 로그, Vercel 배포 로그, Stripe 웹훅(물론 기술적으로는 SSE가 아니지만 같은 아이디어), 그리고 당신이 매일 사용하는 대부분의 "알림" 기능을 지원한다.
- "SSE는 HTTP/2와 호환되지 않는다." HTTP/2에서는 더 잘 작동한다 — 6-연결 제한 없이 모든 스트림이 멀티플렉싱된다.
- "SSE는 텍스트만 사용하므로 이진 데이터를 보낼 수 없습니다." 참, 하지만 JSON 필드에 작은 이진 데이터를 base64로 인코딩하는 것은 괜찮으며, 실제 이진 스트림을 사용해야 할 경우에는 WebSocket을 사용하거나 그냥 HTTP 다운로드를 사용해야 합니다.
-
"SSE는 ping/pong 기능이 없습니다." 15초마다 주석 줄(
:heartbeat\n\n)을 전송하세요. 끝.
FAQ
SSE가 긴 폴링과 같은 것입니까?
넘버 롱 폴링 = 클라이언트가 요청을 하면, 서버는 데이터가 있을 때까지 연결을 열어두고, 서버가 응답하면 클라이언트는 다음 요청을 합니다. SSE = 하나의 요청이 영원히 열어두어지고, 서버는 데이터가 있을 때 푸시합니다. 롱 폴링은 매번 연결을 재개합니다. SSE는 그렇지 않습니다.
SSE가 모바일 브라우저에서 작동하나요?
네, iOS Safari, Chrome Android, Firefox Mobile에서 완전한 지원을 합니다. 자동 재연결은 셀룰러 핸오프를 잘 처리합니다.
React / Vue / Svelte / Solid에서 SSE를 사용할 수 있나요?
네, 어떤 구독을 사용하는 것과 정확히 같습니다. EventSource를 useEffect (또는 동등한) 안에 생성하고, 정리 시 닫고, 각 메시지에 상태를 설정하세요.
서버는 동시에 최대 몇 개의 SSE 연결을 처리할 수 있나요?
Node.js와 합리적인 설정으로, 각 프로세스당 수만 개 — 연결은 대부분 비활성화되어 있고 각각은 몇 KB의 RAM을 소비합니다. 실제 최대 한도인 각 프로세스당 ~32k-65k 개의 열린 파일 설명자와 비교해보세요.
EventSource을 사용해야 할까요, 아니면 fetch + ReadableStream을 사용해야 할까요?
Cookie 인증이 작동한다면 EventSource을 사용하세요 (더 간단하며, 무료 자동 재연결).fetch 필요하다면 Authorization 헤더나 맞춤 요청 동작을 사용하고 재연결을 처리하는 라이브러리를 가져옵니다.
원래 AllDevToolsHub에서 게시되었습니다. 250개 이상의 무료, 프라이버시 우선 브라우저 기반 개발 도구 — SSE 테스터, 웹소켓 테스터, JWT 디코더 등 —는 alldevtoolshub.com를 참고하세요.












