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

Next.js에서는 Claude 호출을 서버 경계 안에 가둬

~16 min · next-js, edge, server-components, rsc

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

키를 가진 코드는 서버에만 둬

App Router에서는 SDK 호출이 Server Component, Server Action, Route Handler 중 하나에 있어야 해. Client Component에서 SDK를 불러오면 API 키가 브라우저 번들로 새어 나갈 수 있어. cwk-site도 app/api/*/route.ts를 외부 API 호출의 기준 집으로 삼아.

Edge와 Node의 제약을 구분해

Edge는 시작이 빠르고 Vercel의 분산 환경에서 돌지만 fetchReadableStream 같은 웹 표준만 쓸 수 있고 Node의 파일시스템은 없어. Messages의 일반 호출과 스트리밍은 가능하지만 Files API 업로드 같은 일부 도우미는 Node가 필요해. 필요한 기능이 분명할 때 런타임을 바꿔.

세 캐시는 서로 다른 중복을 없애

정적 프롬프트 자료에는 Next.js 캐시, 길고 안정된 앞부분에는 Anthropic 프롬프트 캐시, 같은 사용자 입력의 반복에는 애플리케이션 응답 캐시를 쓸 수 있어. 각각 실패와 무효화 조건이 다르므로 하나로 뭉개지 말고 소유 범위를 나눠.

원칙: SDK 호출은 서버 쪽에만 둬. 'use client' 파일에서 import가 보이면 경계를 옮겨.

Code

Prompt caching 가진 Server Action·typescript
// app/actions/summarize.ts
'use server';

import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();
const SYSTEM_PERSONA = await fetch(new URL('./persona.md', import.meta.url)).then(r => r.text());

export async function summarize(text: string) {
  const resp = await client.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: 512,
    system: [
      {
        type: 'text',
        text: SYSTEM_PERSONA,
        cache_control: { type: 'ephemeral' },
      },
    ],
    messages: [{ role: 'user', content: text }],
  });
  const block = resp.content.find(b => b.type === 'text');
  return block && block.type === 'text' ? block.text : '';
}
Edge 런타임 스트리밍 Route Handler·typescript
// app/api/chat/route.ts
import Anthropic from '@anthropic-ai/sdk';

export const runtime = 'edge';
export const dynamic = 'force-dynamic';

const client = new Anthropic();

export async function POST(req: Request) {
  const { messages } = (await req.json()) as { messages: Anthropic.MessageParam[] };

  const stream = await client.messages.stream({
    model: 'claude-sonnet-4-6',
    max_tokens: 1024,
    messages,
    signal: req.signal,
  });

  return new Response(stream.toReadableStream(), {
    headers: { 'content-type': 'text/event-stream' },
  });
}

External links

Exercise

Client Component에 있던 Anthropic 호출 하나를 Server Action이나 Route Handler로 옮겨. 빌드 뒤 .next/static에서 ANTHROPIC_API_KEY를 찾아 브라우저 묶음에 없음을 확인해.
Hint
키가 브라우저에서 도달할 수 없는 상태인지 빌드 산출물로 증명해.

Progress

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

댓글 0

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

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