본문 바로가기
C.W.K.
Stream
Lesson 02 of 06 · published

Node와 Next.js 스트리밍은 취소 신호까지 이어 줘

~16 min · streaming, next-js, route-handler

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

비동기 반복자로 사건을 읽어

client.messages.stream(...)은 타입이 있는 사건의 비동기 반복자와 finalMessage() 약속을 제공해. for await로 델타를 처리하고, 반복이 끝난 뒤 최종 메시지를 기다려 사용량과 종료 상태를 확인해. Python 도우미와 개념은 같지만 컴파일러가 사건 타입을 좁혀 줘.

Route Handler의 응답 스트림으로 건네

Next.js App Router의 Route Handler는 ReadableStream을 본문으로 가진 Response를 반환할 수 있어. SDK 텍스트 스트림을 그 응답에 연결하면 브라우저는 토큰이 도착하는 대로 그려. cwk-site와 Vercel Edge에서도 같은 전송 모양을 쓸 수 있어.

탭이 닫히면 상류 생성도 멈춰

클라이언트가 연결을 끊었는데 서버가 계속 토큰을 만들면 비용만 남아. 요청의 AbortSignal을 SDK 호출의 signal에 전달해 브라우저 취소를 Anthropic 요청까지 이어 줘. 화면·서버·상류 API의 수명을 한 신호로 묶는 거야.

원칙: 스트리밍도 일반 HTTP 작업처럼 역압력과 취소를 설계해. SDK 호출에서 연결 고리를 끊지 마.

Code

Typed 이벤트로 stream iteration·typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();

const stream = await client.messages.stream({
  model: 'claude-sonnet-4-6',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Three streaming tips.' }],
});

for await (const event of stream) {
  if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
    process.stdout.write(event.delta.text);
  }
}
const final = await stream.finalMessage();
console.log('\nusage:', final.usage);
Next.js App Router 스트리밍 Route Handler·typescript
// app/api/chat/route.ts (Next.js 16, App Router)
import Anthropic from '@anthropic-ai/sdk';

export const runtime = 'nodejs'; // 또는 테스트했으면 'edge'

const client = new Anthropic();

export async function POST(req: Request) {
  const { user } = await req.json();

  const stream = new ReadableStream<Uint8Array>({
    async start(controller) {
      const enc = new TextEncoder();
      const upstream = await client.messages.stream({
        model: 'claude-sonnet-4-6',
        max_tokens: 2048,
        messages: [{ role: 'user', content: user }],
        signal: req.signal, // 브라우저 disconnect 시 upstream 취소
      });
      try {
        for await (const event of upstream) {
          if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
            controller.enqueue(enc.encode(event.delta.text));
          }
        }
      } finally {
        controller.close();
      }
    },
  });

  return new Response(stream, {
    headers: { 'content-type': 'text/plain; charset=utf-8' },
  });
}

External links

Exercise

Claude 답을 브라우저로 스트리밍하고 요청 취소가 상류 생성까지 끊는 Next.js Route Handler를 만들어. 중간에 탭을 닫고 서버 로그에서 중단을 확인해.
Hint
상류 중단이 보이지 않으면 messages.stream()req.signal을 전달했는지 봐.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.