Skip to content

블로그에 AI 기능 붙이기 (1편) - 월 35원으로 OpenAI + Netlify Functions

블로그에 AI 기능 붙이기 (1편) - 월 35원으로 OpenAI + Netlify Functions




TL;DR

  • 문제: 코드 블록이 많은 기술 블로그, 독자가 코드만 보고 이해하기 어려워 이탈
  • 원인: 수동으로 모든 코드에 주석 달기는 시간 부족, 함수형 프로그래밍 등 낯선 패러다임은 특히 어려움
  • 해결: OpenAI API + Netlify Functions로 “AI 설명” 버튼 구현, 클릭하면 자동으로 설명 생성
  • 효과: 독자 편의성 ↑, 코드 이해도 ↑, 월 35원 비용으로 AI 기능 추가
  • 한계: API 키 노출 위험 (Netlify Functions 필수), 초기 응답 2초 (OpenAI API 속도), 비용 증가 가능성




글 머리말

“이 코드가 뭐 하는 건지 모르겠어요.”

함수형 프로그래밍 포스팅을 작성하고 팀 동료에게 리뷰를 부탁했을 때 받은 피드백입니다. Stream API 코드 블록을 10개 넘게 올렸는데, 정작 코드만 봐서는 이해가 어렵다는 거였죠.

솔직히 저도 다른 블로그에서 코드 블록만 있는 글을 보면 스크롤을 넘기게 됩니다. 블로그 독자들도 마찬가지일 거라 생각했습니다.

그래서 코드 블록마다 AI 설명 버튼을 달기로 했습니다. 구글에 “gatsby openai integration”을 검색했지만 원하는 예제가 없더군요. 직접 만들어보기로 했습니다.


시리즈 구성:

  1. (1편) OpenAI + Netlify Functions로 AI 코드 설명 구현 ⬅ 현재 글
  2. (2편) MCP 서버로 Claude가 블로그 읽게 만들기
  3. (3편) 영구 캐싱으로 비용 99% 줄이기




배경: 왜 AI 코드 설명 기능인가

저는 코드 중심으로 설명하는 편입니다. 글보다 코드가 명확하다고 생각하거든요. 하지만 코드 블록이 많아질수록 독자 이탈 가능성도 높아집니다. 특히 함수형 프로그래밍처럼 낯선 패러다임은 코드만 봐서는 이해하기 어렵습니다.

처음에는 수동으로 주석을 달아볼까 생각했습니다.

// 주석이 너무 많으면 오히려 가독성 저하
orders.stream()
  .filter(order -> order.getStatus() == OrderStatus.COMPLETED) // 완료된 주문만
  .flatMap(order -> order.getItems().stream()) // 주문 아이템 추출
  .map(OrderItem::getProduct) // 상품 정보만
  // ... 계속

솔직히 보기만 해도 피곤합니다. 그렇다고 모든 코드에 주석을 다는 것도 번거롭고, 글이 너무 길어집니다.


그러다가 유튜브에서 우연히 “OpenAI API를 활용한 문서 자동화” 발표를 봤습니다.

코드 블록마다 버튼을 달아서, 클릭하면 AI가 설명해주는 방식이면 어떨까?





설계 논의: 어떤 방식을 선택할까

아이디어가 정해지니 이제 구현 방법을 고민했습니다. 세 가지 방법을 비교해봤습니다.

방식장점단점예상 시간
완전 수동무료, 설명 품질 보장과거 포스팅 적용 불가코드 1개당 3분 × 300개 = 15시간
OpenAI API자동 생성, 과거 글 적용 가능비용 발생초기 구현 2시간
하이브리드점진적 비용 절감구현 복잡도 증가초기 구현 5시간

최종 선택: OpenAI API

저는 OpenAI API를 선택했습니다. 이유는 단순합니다. 비용이 예상보다 훨씬 저렴했거든요.

실제 비용 분석 (GPT-4o-mini)
코드 설명 1회: 약 ₩0.12
월 예상 (300회): 약 ₩36
첫 $5 크레딧: 약 6개월 무료

자판기 음료 한 캔의 1/10 가격으로 블로그에 AI 기능을 추가할 수 있습니다.


게다가 로컬 스토리지 캐싱을 적용하면 재방문자는 API 호출이 없습니다. 실제 비용은 더 적을 겁니다.

결정적으로 과거 포스팅 50개에도 모두 적용 가능하다는 점이었습니다. 수동으로 300개 코드 블록을 설명하려면 15시간은 걸릴 테니까요.


의사결정 과정을 정리하면 이렇습니다.

graph TD
    A[문제: 코드 이해 어려움] --> B{해결 방법?}
    B -->|수동 주석| C[15시간 소요]
    B -->|OpenAI API| D[초기 구현 2시간]
    B -->|하이브리드| E[구현 5시간]

    C --> F{시간 vs 비용}
    D --> F
    E --> F

    F --> G[OpenAI 선택]

    G --> H[시간 절약]
    G --> I[과거 글 적용]
    G --> J[캐싱 활용]

    style G fill:#d4edda
    style C fill:#f8d7da
    style D fill:#fff3cd




구현: 아키텍처 설계

방향이 정해졌으니 이제 구현입니다. 시스템을 설계할 때 가장 중요하게 생각한 건 비용 최소화사용자 경험 두 가지였습니다.

graph TB
    A[독자: AI 설명 클릭] --> B{로컬 스토리지<br/>캐시 확인}
    B -->|캐시 있음| C[즉시 표시 ✅<br/>API 호출 없음]
    B -->|캐시 없음| D[Netlify Function 호출]

    D --> E{Rate Limiting<br/>IP당 분당 10회}
    E -->|통과| F[OpenAI API 호출<br/>GPT-4o-mini]
    E -->|초과| G[429 Error]

    F --> H[설명 생성 완료]
    H --> I[로컬 스토리지 저장]
    H --> J[화면 표시]

    style C fill:#d4edda
    style F fill:#fff3cd
    style G fill:#f8d7da
    style I fill:#e3f2fd

핵심 설계 원칙:

  1. 클라이언트 캐싱 우선: 같은 코드는 API 재호출 안 함
  2. Rate Limiting: IP당 분당 10회로 비용 폭발 방지
  3. 서버리스: 별도 서버 없이 Netlify Functions 활용

왜 Netlify Functions인가

블로그는 Gatsby로 만들어져 있고, Netlify에 배포 중입니다.

💡Netlify Functions 선택 이유

장점:

  • 별도 서버 불필요 (서버리스)
  • 무료 티어: 월 12만 5천 회 호출
  • netlify dev로 로컬 테스트 간편
  • OpenAI API 키를 환경 변수로 안전하게 관리

단점:

  • 콜드 스타트 (첫 호출 시 200-300ms 추가)
  • 복잡한 로직엔 부적합

결론: 간단한 API 프록시 용도로는 완벽





구현: 단계별 가이드

1단계: Netlify Function 작성

배경 설명

Netlify Functions는 AWS Lambda의 래퍼입니다. 서버 없이 API를 만들 수 있어서 간편합니다.

코드 구현

// netlify/functions/explain-code.js
exports.handler = async event => {
  // CORS 처리
  const headers = {
    'Access-Control-Allow-Origin': '*',
    'Content-Type': 'application/json',
  }

  // Rate Limiting (IP당 분당 10회)
  const clientIp = event.headers['x-forwarded-for']
  const rateLimitKey = `rate-limit-${clientIp}`

  // ... Rate Limit 체크 로직 (메모리 캐시 또는 Redis)

  // Request Body 파싱
  const { code, language } = JSON.parse(event.body)

  // OpenAI API 호출
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages: [
        {
          role: 'system',
          content:
            '친절한 코드 설명 전문가입니다. 2-4문장으로 핵심만 설명합니다.',
        },
        {
          role: 'user',
          content: `다음 ${language} 코드를 쉽게 설명해주세요:\n\n${code}`,
        },
      ],
      max_tokens: 300, // 비용 통제
      temperature: 0.3, // 일관성 확보
    }),
  })

  const data = await response.json()

  return {
    statusCode: 200,
    headers,
    body: JSON.stringify({
      explanation: data.choices[0].message.content,
      source: 'ai',
    }),
  }
}

max_tokens: 300으로 설명 길이를 제한해 비용을 통제하고, temperature: 0.3으로 일관성을 높였습니다. Rate Limiting은 IP당 분당 10회로 설정했습니다.

2단계: 클라이언트 캐싱 전략

배경 설명

클라이언트에서 API를 호출할 때 로컬 스토리지를 먼저 확인하고, 없을 때만 API를 호출해야 합니다. 이렇게 하면 재방문자는 API 비용이 발생하지 않습니다.

캐싱 로직 핵심

// 캐싱 전략의 핵심 로직
const cacheKey = `code-explain-${codeHash}`

// 1. 캐시 확인
const cached = localStorage.getItem(cacheKey)
if (cached) {
  const { explanation } = JSON.parse(cached)
  return explanation // API 호출 없이 즉시 반환
}

// 2. API 호출 (캐시 없을 때만)
const response = await fetch('/.netlify/functions/explain-code', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ code, language }),
})
const data = await response.json()

// 3. 결과 캐싱
localStorage.setItem(
  cacheKey,
  JSON.stringify({
    explanation: data.explanation,
    timestamp: Date.now(),
  })
)

핵심 포인트:

  • 같은 코드는 같은 해시값으로 캐시 키 생성
  • 로컬 스토리지에 저장해 재방문 시 API 호출 없이 즉시 표시
  • 이 캐싱 전략으로 실제 API 호출 횟수가 예상의 40% 수준으로 줄었습니다




예상치 못한 문제들

구현하면서 몇 가지 삽질을 했습니다. 저와 같은 실수를 피하시길 바랍니다.

문제 1: Gatsby 개발 서버에서 Functions가 안 됨

처음에 npm run develop로 실행하고 버튼을 눌렀더니 404 에러가 떴습니다.

한참 헤매다가 알았는데, Gatsby 개발 서버는 정적 페이지만 실행합니다. Netlify Functions는 별도로 실행해야 하더군요.

⚠️해결 방법
# Before
npm run develop  # ❌ Functions 안 됨

# After
netlify dev      # ✅ Functions 포함

netlify dev를 사용하면 Gatsby 서버와 Functions를 동시에 실행합니다.


문제 2: 디버깅이 어려운 에러 메시지

API 호출 실패 시 “설명 생성에 실패했습니다”라는 메시지만 떴습니다. 원인을 알 수 없어서 디버깅이 어려웠습니다.

💡해결 방법
// Before
throw new Error('설명 생성에 실패했습니다.')

// After
const errorData = await response.json().catch(() => ({}))
throw new Error(
  errorData.error || errorData.message || '설명 생성에 실패했습니다.'
)

이제 OpenAI API의 실제 에러 메시지를 볼 수 있습니다.





이 접근의 아쉬운 점

구현하면서 몇 가지 중요한 선택을 해야 했습니다. 각각의 trade-off를 정리합니다.

1. GPT-4 vs GPT-4o-mini

처음에는 GPT-4를 쓸까 고민했습니다. 설명 품질이 더 좋을 테니까요.

하지만 비용을 비교해보니 20배 차이가 났습니다.

모델코드 설명 1회월 300회품질
GPT-4약 ₩2.4약 ₩720⭐⭐⭐⭐⭐
GPT-4o-mini약 ₩0.12약 ₩36⭐⭐⭐⭐

테스트해보니 GPT-4o-mini도 충분히 좋은 설명을 만들더군요. 2-4문장으로 핵심만 전달하는 용도라면 mini가 훨씬 합리적입니다.


2. 실시간 vs 빌드 타임 생성

빌드 타임에 미리 생성할까 생각해봤습니다. Gatsby 빌드 시 모든 코드 블록의 설명을 미리 만들어두면 비용이 제로입니다.

하지만 포기했습니다.

이유:

  • 포스팅 수정할 때마다 전체 빌드 필요
  • 코드 블록 300개면 빌드 시간 10분+
  • 배포 속도 저하

차라리 실시간 생성 + 캐싱이 합리적이라고 판단했습니다.


3. 로컬 스토리지 vs 서버 사이드 캐싱

현재는 로컬 스토리지만 사용합니다. 이게 어떤 의미일까요?

graph LR
    A[독자 A 방문] -->|AI 설명 클릭| B[API 호출 ₩0.12]
    C[독자 B 방문] -->|AI 설명 클릭| D[API 호출 ₩0.12]
    E[독자 C 방문] -->|AI 설명 클릭| F[API 호출 ₩0.12]
    G[독자 1000명] --> H[총 비용 ₩120]

    style B fill:#ffcdd2
    style D fill:#ffcdd2
    style F fill:#ffcdd2
    style H fill:#f8d7da

같은 코드 블록인데도 1000번 API를 호출합니다.


서버 사이드 캐싱이라면?

graph LR
    A[독자 A 방문] -->|AI 설명 클릭| B[API 호출 ₩0.12<br/>DB 저장]
    C[독자 B 방문] -->|AI 설명 클릭| D[DB에서 가져옴 ₩0]
    E[독자 C 방문] -->|AI 설명 클릭| F[DB에서 가져옴 ₩0]
    G[독자 1000명] --> H[총 비용 ₩0.12]

    style B fill:#fff3cd
    style D fill:#c8e6c9
    style F fill:#c8e6c9
    style H fill:#d4edda

1000배 차이입니다.


⚠️왜 서버 캐싱을 안 했나요?

솔직히 말하면, 우선순위가 아니었습니다.

제 블로그는 일일 방문자 약 50명, 코드 설명 클릭률 5% 정도입니다. 월 예상 API 호출이 100회 정도인데, 비용은 껌 한 통 값도 안 됩니다.

몇십 원을 절약하려고 2-3시간을 투자하기보다, 글 쓰는 데 시간을 쓰고 싶었습니다.


하지만 트래픽이 늘어나면 서버 캐싱이 합리적입니다. 3편에서 Supabase로 구현한 영구 캐싱 시스템을 다룰 예정입니다.





실제 적용 결과

비용 분석

지난 한 달간 실제 비용을 측정해봤습니다.

항목수치
총 API 호출약 180회
실제 비용$0.016 (약 ₩21)
예상 대비-42%

예상했던 것보다 훨씬 적습니다. 로컬 스토리지 캐싱 덕분에 재방문자는 API 호출이 없거든요.


사용자 경험

정량적으로 측정하기는 어렵지만, 몇 가지 변화가 있었습니다.

지표BeforeAfter
평균 체류 시간1분 30초2분 00초
코드 블록 스크롤빠르게 넘김멈춰서 읽음
동료 피드백“복잡해요”“이해하기 편해요”

데이터가 더 쌓여야 확실히 알 수 있겠지만, 방향은 맞는 것 같습니다.





내 프로젝트에 바로 적용하기

체크리스트

  • OpenAI API 키 발급 받았는가?
  • Netlify 계정과 배포 환경이 준비되었는가?
  • 환경 변수 설정 (OPENAI_API_KEY)을 완료했는가?
  • Rate Limiting 로직을 추가했는가?
  • 로컬에서 netlify dev로 테스트했는가?

주의사항

🚨절대 하지 말 것

❌ API 키를 코드에 하드코딩

  • 절대 금지! 환경 변수로 관리하세요

❌ Rate Limiting 없이 배포

  • 누군가 스크립트로 반복 호출하면 비용 폭발

❌ 캐싱 없이 사용

  • 같은 코드 설명이 매번 API 호출됨

❌ max_tokens 미설정

  • 설명이 너무 길어져서 비용 증가

추천 설정

검증된 설정값
// OpenAI API 설정
{
  model: 'gpt-4o-mini',        // 비용 효율적
  max_tokens: 300,              // 설명 길이 제한
  temperature: 0.3,             // 일관성 확보
}

// Rate Limiting
{
  maxRequests: 10,              // IP당 분당 10회
  windowMs: 60 * 1000,          // 1분
}

// 캐싱
{
  storage: 'localStorage',      // 브라우저 캐시
  ttl: null,                    // 영구 보존 (3편 참조)
}

트러블슈팅

Q. “로컬에서 Functions가 안 돼요”

  • netlify dev 사용하셨나요?
  • npm run develop은 Functions를 실행하지 않습니다

Q. “API 호출이 너무 느려요”

  • OpenAI API는 보통 2-3초 소요됩니다
  • 로딩 상태를 표시해서 UX 개선하세요

Q. “비용이 예상보다 많이 나와요”

  • Rate Limiting을 확인하세요
  • max_tokens를 300으로 제한했는지 확인하세요
  • 캐싱이 제대로 작동하는지 확인하세요




다음 편 예고

AI 코드 설명 기능을 만들고 나니, 문득 이런 생각이 들었습니다.

“Claude가 내 블로그 전체를 읽을 수 있다면?”

글을 쓸 때 “이 주제를 이미 다뤘나?”, “비슷한 패턴으로 쓴 글이 있나?” 같은 질문을 하고 싶을 때가 있습니다. 하지만 Claude는 제 블로그를 모릅니다.

그래서 MCP(Model Context Protocol) 서버를 만들어보기로 했습니다. 다음 편에서는 MCP 서버 구축, Claude Desktop 연동, 실제 활용 사례를 다룹니다.





시스템 점검 체크리스트

저도 배포 전에 이 항목들을 꼭 확인합니다. OpenAI + Netlify Functions를 사용한다면 참고하시면 좋을 것 같습니다.

  • API 키 보안: OpenAI API 키가 Netlify Functions 환경변수에만 있고, 클라이언트 코드에 노출되지 않는가?
  • 에러 핸들링: API 호출 실패 시 사용자에게 적절한 에러 메시지를 표시하는가?
  • 로딩 UI: API 호출 중 로딩 스피너를 표시하여 사용자가 기다리게 하는가?
  • 토큰 제한: 코드 길이가 너무 길면 잘라내거나 제한하는 로직이 있는가? (토큰 폭탄 방지)
  • 비용 모니터링: OpenAI API 사용량을 주기적으로 확인하고 있는가?



마무리

처음에는 단순히 “독자가 코드를 이해하기 쉽게” 하려고 시작했습니다. 하지만 만들면서 몇 가지 깨달은 게 있습니다.

AI 기능 추가는 생각보다 간단합니다. OpenAI API 문서 읽고, Netlify Functions 파일 하나 만들고, React 컴포넌트 붙이면 끝입니다. 대단한 ML 지식이나 프롬프트 엔지니어링이 필요하지 않았습니다. 2시간 정도면 충분합니다.

비용 걱정은 과대평가되어 있습니다. 저도 처음엔 “API 비용이 눈덩이처럼 불어나면 어쩌지?” 걱정했는데, 실제로는 월 몇십 원 수준이었습니다. 오히려 커피 한 잔 값으로 블로그 품질을 높일 수 있다면 충분히 가치 있습니다.

완벽한 설계를 기다리지 마세요. 저도 “서버 캐싱을 처음부터 할걸…” 하는 생각이 들었지만, 빠르게 시작한 덕분에 2개월 동안 실제 사용 데이터를 모을 수 있었습니다. 트래픽이 늘어나면 그때 3편에서 다룰 서버 캐싱을 추가하면 됩니다.

결국 가장 중요한 건 일단 만들어보는 것입니다. 비슷한 고민을 하시는 분들께 도움이 되었으면 합니다.





참고 :

OpenAI API 공식 문서
Netlify Functions 가이드
Gatsby 플러그인 개발 가이드
React Hooks 패턴




읽어주셔서 감사합니다.🖐


Ramsbaby
Written byRamsbaby
이 블로그는 직접 개발/운영하는 블로그이므로 당신을 불쾌하게 만드는 불필요한 광고가 없습니다.

#My Github#소개 페이지#Blog OpenSource Github#Blog OpenSource Demo Site