Skip to content

정적 블로그에서 백엔드 통신하기 - 일반 웹과의 결정적 차이

정적 블로그에서 백엔드 통신하기 - 일반 웹과의 결정적 차이




TL;DR

  • 문제: 정적 블로그에서 API 키를 프론트엔드에 넣으면 개발자 도구에 그대로 노출됨
  • 원인: 로그인이 없으니 사용자 인증(JWT)이 불가능, 백엔드 서버도 없음
  • 해결: Netlify Functions로 API 키를 환경변수에 숨기고 서버리스 백엔드 구축
  • 효과: API 키 보호 + 서버 운영 부담 제로 + 월 100원 미만 비용
  • 한계: 콜드 스타트(최대 2초), Netlify 종속성, 복잡한 로직엔 부적합




글 머리말

“왜 Netlify Functions를 써야 하나요? 그냥 axios로 API 호출하면 안 되나요?”

혼자 블로그를 개발하다가 문득 이런 의문이 들었습니다. 저는 7년 동안 일반 웹 애플리케이션을 개발해왔거든요. 로그인하면 JWT 토큰 받고, 그 토큰으로 백엔드 API 호출하고. 너무 당연하게 해왔던 방식이죠.

그런데 정적 블로그로 넘어오니까 당연했던 것들이 작동하지 않더군요. 로그인? 없습니다. JWT 토큰? 발급할 수 없습니다. 백엔드 서버? 그것도 없습니다.

처음에는 “그냥 프론트엔드에서 API 키 넣어서 호출하면 되지 않나?” 싶었습니다. 실제로 그렇게 했다가 크롬 개발자 도구에서 API 키가 그대로 노출되는 걸 보고 식겁했습니다.

이번 글에서는 일반 웹 애플리케이션과 정적 블로그의 백엔드 통신 방식 차이를 정리해보았습니다. 저처럼 “왜 굳이 Netlify Functions를 써야 하지?”라는 의문이 드셨다면, 이 글이 답이 될 겁니다.





일반 웹 애플리케이션의 통신 구조

로그인 기반 인증

일반 웹 애플리케이션(React SPA, Vue.js 등)에서는 이런 흐름으로 작동합니다.

sequenceDiagram
    participant U as 사용자
    participant F as 프론트엔드
    participant B as 백엔드 서버
    participant DB as 데이터베이스

    U->>F: 1. 로그인 (ID/PW)
    F->>B: 2. POST /api/login
    B->>DB: 3. 사용자 인증
    DB-->>B: 4. 인증 성공
    B-->>F: 5. JWT 토큰 발급
    F->>F: 6. 토큰 저장

    U->>F: 7. 데이터 요청
    F->>B: 8. GET /api/data + JWT
    B->>B: 9. JWT 검증
    B->>DB: 10. 데이터 조회
    DB-->>B: 11. 결과 반환
    B-->>F: 12. 데이터 전달
    F-->>U: 13. 화면 표시

코드 예시: 일반 웹 애플리케이션

// 1. 로그인
async function login(username, password) {
  const response = await axios.post('/api/login', { username, password })
  const { token } = response.data

  // 2. JWT 토큰 저장
  localStorage.setItem('authToken', token)
}

// 3. API 호출 시 토큰 사용
async function fetchUserData() {
  const token = localStorage.getItem('authToken') // 사용자별 인증키

  const response = await axios.get('/api/user/profile', {
    headers: {
      Authorization: `Bearer ${token}`, // 개인별 인증 토큰
    },
  })

  return response.data
}

이 방식의 핵심은 사용자별 인증입니다.

구성 요소역할
JWT 토큰사용자를 식별하는 고유한 인증키
localStorage브라우저에 토큰 저장
Authorization Header모든 API 요청에 토큰 포함
백엔드 검증토큰이 유효한지 확인 후 데이터 제공

너무 익숙한 흐름이죠. 저도 수년간 이 방식으로 개발해왔습니다.


API 키는 어디에 저장할까?

일반 웹 애플리케이션에서 Supabase 같은 외부 API를 호출할 때는 이렇게 합니다.

// 절대 이렇게 하면 안 됨!
const SUPABASE_KEY = 'eyJhbGciOi...' // 소스코드에 노출!

async function saveData(email) {
  await fetch('https://api.supabase.co/rest/v1/subscribers', {
    headers: {
      apikey: SUPABASE_KEY, // 개발자 도구에서 보임!
      Authorization: `Bearer ${SUPABASE_KEY}`,
    },
    body: JSON.stringify({ email }),
  })
}

문제점:

  • 소스코드가 브라우저에 전부 노출됨
  • 개발자 도구에서 API 키를 복사할 수 있음
  • 불특정 다수가 API 키를 악용 가능

해결책: 백엔드 서버에서만 API 키 사용

// 프론트엔드 (API 키 없음)
async function saveData(email) {
  await fetch('/api/newsletter', {
    // 내 서버로 요청
    method: 'POST',
    body: JSON.stringify({ email }),
  })
}

// 백엔드 (Node.js 서버)
app.post('/api/newsletter', async (req, res) => {
  const { email } = req.body
  const SUPABASE_KEY = process.env.SUPABASE_KEY // 환경변수 (서버에만 존재)

  await fetch('https://api.supabase.co/rest/v1/subscribers', {
    headers: {
      apikey: SUPABASE_KEY,
      Authorization: `Bearer ${SUPABASE_KEY}`,
    },
    body: JSON.stringify({ email }),
  })

  res.json({ success: true })
})

장소API 키 위치노출 여부
프론트엔드 (브라우저)API 키 없음소스코드 공개
백엔드 (서버)환경변수외부 접근 불가

핵심 요약

  • 사용자별 인증: JWT 토큰으로 “누가 요청하는지” 식별
  • API 키 보호: 백엔드 서버의 환경변수에 저장
  • 프론트엔드는 깨끗: 민감한 정보 없이 내 서버만 호출




정적 블로그의 딜레마

백엔드 서버가 없다

정적 블로그(Gatsby, Next.js SSG 모드)는 빌드 시점에 HTML 파일로 변환됩니다.

# 빌드 과정
gatsby build
→ src/ (React 코드) → public/ (정적 HTML/CSS/JS)

# 결과물
public/
├── index.html
├── about/index.html
├── blog/post-1/index.html
└── static/
    └── app.js  ← 이 파일이 브라우저에서 실행됨

문제:

  • Node.js 서버가 없음 (서버 API 엔드포인트 없음)
  • 모든 JavaScript 코드가 브라우저에서 실행됨
  • API 키를 코드에 넣으면 누구나 볼 수 있음

처음에 저도 “환경변수 쓰면 되지 않나?” 싶었습니다. 하지만 Gatsby에서 환경변수를 쓰면 빌드 시점에 값이 코드에 박히게 됩니다. 결국 브라우저에서 전부 노출되는 거죠.


그럼 이렇게 하면 어떻게 될까요?

// 정적 블로그에서 절대 금지!
const GA4_KEY = 'YOUR_GOOGLE_API_KEY'

function BlogStats() {
  const [stats, setStats] = useState({})

  useEffect(() => {
    fetch(
      'https://analyticsdata.googleapis.com/v1beta/properties/123456789:runReport',
      {
        headers: {
          Authorization: `Bearer ${GA4_KEY}`, // 크롬 개발자 도구에 노출!
        },
      }
    )
      .then(res => res.json())
      .then(setStats)
  }, [])

  return <div>{stats.todayVisitors}</div>
}

결과:

  • 빌드된 app.js 파일에 API 키가 그대로 포함됨
  • 누구나 개발자 도구에서 키를 복사할 수 있음
  • 불특정 다수의 악의적 사용 가능 (API 할당량 소진, 데이터 삭제 등)

저도 처음에 이렇게 했다가 GA4 API 키가 노출되는 걸 보고 깜짝 놀랐습니다. 다행히 배포 전에 발견해서 큰 사고는 없었지만요.


로그인도 없다

일반 웹과 달리 블로그는 누구나 접속 가능합니다.

일반 웹정적 블로그
로그인 필요로그인 없음
JWT 토큰으로 사용자 식별사용자 구분 불가
토큰 없으면 API 호출 차단차단 메커니즘 없음
사용자별 권한 관리권한 개념 없음

정적 블로그의 특성:

  • URL만 알면 누구나 접속 가능
  • 사용자 인증 개념이 없음
  • JWT 토큰 발급 불가능
  • 백엔드 서버가 없어서 환경변수 사용 불가

그럼 어떻게 해야 할까요? 여기서 Netlify Functions가 등장합니다.


핵심 요약

  • 서버가 없음: 빌드 결과물은 정적 파일뿐
  • API 키 노출 위험: 코드에 넣으면 전부 공개
  • 로그인 없음: 사용자별 인증 불가능




Netlify Functions - 서버리스 백엔드

서버 없이 서버 기능 사용하기

Netlify Functions는 서버를 직접 운영하지 않고도 서버 기능을 사용할 수 있게 해줍니다.

graph TB
    subgraph 정적 블로그 구조
        A[정적 HTML/CSS/JS]
        B[Netlify Functions]
        C[환경변수 저장소]
    end

    subgraph 외부 서비스
        D[Supabase]
        E[SendGrid]
        F[Google Analytics]
    end

    A -->|API 키 없음| B
    B -->|환경변수 사용| C
    C -.->|API 키 저장| B
    B -->|인증된 요청| D
    B -->|인증된 요청| E
    B -->|인증된 요청| F

    style A fill:#e3f2fd
    style B fill:#fff3e0
    style C fill:#f1f8e9

핵심 개념:

  • 정적 파일은 API 키 없이 Netlify Functions만 호출
  • Netlify Functions는 환경변수에서 API 키를 가져옴
  • 외부 서비스 호출은 Netlify Functions에서만 수행

즉, Netlify Functions가 백엔드 서버 역할을 하는 겁니다. 다만 서버를 직접 운영하지 않아도 되죠.


대안 검토: 왜 Netlify Functions인가?

처음엔 여러 선택지를 고민했습니다.

항목Netlify FunctionsVercel FunctionsAWS LambdaNode.js 서버선택 이유
비용무료 (월 12만 호출)무료 (월 10만 호출)무료 (월 100만 호출)월 $10+ (EC2 t3.micro)✅ 선택
배포자동 (Git push)자동 (Git push)수동 (CI/CD 구성)수동 (SSH 배포)✅ 편함
콜드 스타트1~2초1~2초0.5~1초없음⚠️ 감수
학습 곡선낮음낮음높음 (IAM, VPC 등)중간✅ 쉬움
통합성Gatsby와 완벽 통합Next.js 최적화독립적독립적✅ 선택
운영 부담없음없음낮음높음 (모니터링, 배포)✅ 선택

왜 AWS Lambda를 안 했나?

  • 가장 저렴하고 빠르지만, IAM 설정, VPC 구성, CloudWatch 로그 등 학습 곡선이 가파름
  • 개인 블로그에는 오버엔지니어링

왜 Node.js 서버를 안 했나?

  • 익숙하긴 하지만 서버 운영 부담 (모니터링, 배포, 스케일링, 보안 패치)
  • 월 $10+ 비용 vs 무료 (Netlify) = 손해

왜 Vercel Functions를 안 했나?

  • Vercel은 Next.js에 최적화됨
  • 저는 이미 Gatsby + Netlify 조합을 사용 중

Netlify Functions를 선택한 이유:

  • ✅ Gatsby와 완벽 통합 (빌드 시 자동 배포)
  • ✅ Git push만 하면 배포 완료
  • ✅ 환경변수 관리 UI 제공
  • ✅ 무료 플랜 충분 (월 12만 호출)
  • ✅ 운영 부담 제로

실제 코드 비교

일반 웹 (Node.js 서버)

프로젝트 구조:
frontend/          ← React 코드 (브라우저)
 └── src/
     └── components/

backend/           ← Node.js 서버
 ├── server.js
 ├── routes/
 └── .env          ← API 키 저장

정적 블로그 (Netlify Functions)

프로젝트 구조:
src/               ← React 코드 (빌드되면 HTML/JS)
 └── components/

netlify/           ← 서버리스 함수 (Netlify에서 실행)
 └── functions/
     └── newsletter-submit.js

.env               ← 로컬 개발용 (Git에 업로드 안 함)
Netlify 대시보드   ← 배포 환경 변수 (서버에만 존재)

코드 비교:

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 프론트엔드 (공통 - API 키 없음)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// 일반 웹
async function submitNewsletter(email) {
  await fetch('https://myserver.com/api/newsletter', {
    // 내 Node.js 서버
    method: 'POST',
    body: JSON.stringify({ email }),
  })
}

// 정적 블로그
async function submitNewsletter(email) {
  await fetch('/.netlify/functions/newsletter-submit', {
    // Netlify Function
    method: 'POST',
    body: JSON.stringify({ email }),
  })
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 백엔드 (API 키 사용)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// 일반 웹 - Node.js 서버 (backend/server.js)
app.post('/api/newsletter', async (req, res) => {
  const { email } = req.body
  const SUPABASE_KEY = process.env.SUPABASE_KEY // 환경변수

  await fetch('https://api.supabase.co/rest/v1/subscribers', {
    headers: {
      apikey: SUPABASE_KEY,
      Authorization: `Bearer ${SUPABASE_KEY}`,
    },
    body: JSON.stringify({ email }),
  })

  res.json({ success: true })
})

// 정적 블로그 - Netlify Function (netlify/functions/newsletter-submit.js)
exports.handler = async event => {
  const { email } = JSON.parse(event.body)
  const SUPABASE_KEY = process.env.SUPABASE_KEY // 환경변수 (동일!)

  await fetch('https://api.supabase.co/rest/v1/subscribers', {
    headers: {
      apikey: SUPABASE_KEY,
      Authorization: `Bearer ${SUPABASE_KEY}`,
    },
    body: JSON.stringify({ email }),
  })

  return {
    statusCode: 200,
    body: JSON.stringify({ success: true }),
  }
}

차이점:

구분일반 웹정적 블로그
백엔드 형태Node.js 서버 (EC2, ECS 등)Netlify Functions (서버리스)
서버 관리직접 운영 (EC2, 모니터링, 배포)Netlify가 자동 관리
환경변수 위치서버 .env 또는 환경 설정Netlify 대시보드
비용서버 운영 비용 (월 5만원+)무료 티어 충분
확장성수동 스케일링자동 스케일링

보시다시피 백엔드 로직은 거의 동일합니다. 차이는 서버를 직접 운영하느냐, Netlify가 관리해주느냐 뿐이죠.


환경변수 설정 방법

로컬 개발 (.env 파일)

# .env (Git에 업로드하지 않음!)
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
SENDGRID_API_KEY=SG.aBcDeFgHiJkLmNoPqRsTuVwXyZ...
GA4_CREDENTIALS_JSON=eyJjbGllbnRfZW1haWwiOi...

Netlify 배포 환경

https://app.netlify.com/sites/your-site/settings/deploys
→ Environment variables 섹션
→ Add variable 클릭

Key: SUPABASE_SERVICE_KEY
Value: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Key: SENDGRID_API_KEY
Value: SG.aBcDeFgHiJkLmNoPqRsTuVwXyZ...

중요:

  • .env 파일은 .gitignore에 추가하여 Git에 업로드하지 않음
  • Netlify 대시보드에서 환경변수를 수동으로 설정
  • 환경변수는 Netlify Functions에서만 접근 가능 (브라우저에서 접근 불가)

핵심 요약

  • Netlify Functions = 서버리스 백엔드: 서버 없이 서버 기능 사용
  • 환경변수로 API 키 보호: 브라우저에 노출되지 않음
  • 백엔드 로직은 동일: 다만 서버 운영 부담이 없음




실전 예시: 블로그 통계 기능

요구사항

Google Analytics 4 API를 사용해서 블로그 방문자 통계를 표시합니다.

  • 오늘 방문자 수
  • 총 방문자 수
  • 인기 포스팅 Top 5

실제로 저희 블로그에 구현한 기능입니다.


일반 웹 방식 (Node.js)

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 프론트엔드 (src/components/BlogStats.jsx)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

function BlogStats() {
  const [stats, setStats] = useState({})

  useEffect(() => {
    fetch('https://myserver.com/api/blog-stats') // Node.js 서버
      .then(res => res.json())
      .then(setStats)
  }, [])

  return (
    <div>
      <p>오늘 방문자: {stats.todayVisitors}</p>
      <p>총 방문자: {stats.totalVisitors}</p>
    </div>
  )
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 백엔드 (backend/routes/blog-stats.js)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

const { BetaAnalyticsDataClient } = require('@google-analytics/data')

app.get('/api/blog-stats', async (req, res) => {
  // 환경변수에서 GA4 인증 정보 가져오기
  const credentials = JSON.parse(process.env.GA4_CREDENTIALS_JSON)

  const analyticsDataClient = new BetaAnalyticsDataClient({
    credentials: {
      client_email: credentials.client_email,
      private_key: credentials.private_key,
    },
  })

  // GA4에서 데이터 가져오기
  const [todayData, totalData] = await Promise.all([
    analyticsDataClient.runReport({
      property: `properties/${process.env.GA4_PROPERTY_ID}`,
      dateRanges: [{ startDate: 'today', endDate: 'today' }],
      metrics: [{ name: 'activeUsers' }],
    }),
    analyticsDataClient.runReport({
      property: `properties/${process.env.GA4_PROPERTY_ID}`,
      dateRanges: [{ startDate: '2020-01-01', endDate: 'today' }],
      metrics: [{ name: 'totalUsers' }],
    }),
  ])

  res.json({
    todayVisitors: parseInt(
      todayData[0].rows?.[0]?.metricValues?.[0]?.value || '0'
    ),
    totalVisitors: parseInt(
      totalData[0].rows?.[0]?.metricValues?.[0]?.value || '0'
    ),
  })
})

정적 블로그 방식 (Netlify Functions)

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 프론트엔드 (src/components/BlogStats.jsx)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

function BlogStats() {
  const [stats, setStats] = useState({})

  useEffect(() => {
    fetch('/.netlify/functions/blog-stats') // Netlify Function
      .then(res => res.json())
      .then(setStats)
  }, [])

  return (
    <div>
      <p>오늘 방문자: {stats.todayVisitors}</p>
      <p>총 방문자: {stats.totalVisitors}</p>
    </div>
  )
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Netlify Function (netlify/functions/blog-stats.js)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

const { BetaAnalyticsDataClient } = require('@google-analytics/data')

exports.handler = async event => {
  // 환경변수에서 GA4 인증 정보 가져오기 (일반 웹과 동일!)
  const credentials = JSON.parse(process.env.GA4_CREDENTIALS_JSON)

  const analyticsDataClient = new BetaAnalyticsDataClient({
    credentials: {
      client_email: credentials.client_email,
      private_key: credentials.private_key,
    },
  })

  // GA4에서 데이터 가져오기 (일반 웹과 동일!)
  const [todayData, totalData] = await Promise.all([
    analyticsDataClient.runReport({
      property: `properties/${process.env.GA4_PROPERTY_ID}`,
      dateRanges: [{ startDate: 'today', endDate: 'today' }],
      metrics: [{ name: 'activeUsers' }],
    }),
    analyticsDataClient.runReport({
      property: `properties/${process.env.GA4_PROPERTY_ID}`,
      dateRanges: [{ startDate: '2020-01-01', endDate: 'today' }],
      metrics: [{ name: 'totalUsers' }],
    }),
  ])

  return {
    statusCode: 200,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      todayVisitors: parseInt(
        todayData[0].rows?.[0]?.metricValues?.[0]?.value || '0'
      ),
      totalVisitors: parseInt(
        totalData[0].rows?.[0]?.metricValues?.[0]?.value || '0'
      ),
    }),
  }
}

차이점 요약:

구분일반 웹정적 블로그
프론트엔드 API 호출https://myserver.com/api/blog-stats/.netlify/functions/blog-stats
백엔드 코드app.get('/api/blog-stats', ...)exports.handler = async (event) => ...
응답 방식res.json({ ... })return { statusCode: 200, body: JSON.stringify({ ... }) }
서버 운영EC2, ECS 등 직접 관리Netlify가 자동 관리

핵심:

  • 백엔드 로직은 거의 동일함 (환경변수 사용, API 호출)
  • 차이는 서버 운영 방식 (직접 관리 vs 서버리스)




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

체크리스트

정적 블로그에서 백엔드 통신이 필요할 때:

  • API 키가 필요한 외부 서비스인가?
  • Netlify Functions 폴더 구조가 있는가? (netlify/functions/)
  • .env 파일이 .gitignore에 추가되어 있는가?
  • Netlify 대시보드에 환경변수가 설정되어 있는가?
  • 프론트엔드에서 /.netlify/functions/ 경로로 호출하는가?

구현 순서

💡3단계로 구현하기

1단계: 폴더 구조 생성

mkdir -p netlify/functions

2단계: 함수 파일 생성

// netlify/functions/my-api.js
exports.handler = async event => {
  const API_KEY = process.env.MY_API_KEY

  // 외부 API 호출
  const response = await fetch('https://external-api.com/data', {
    headers: { Authorization: `Bearer ${API_KEY}` },
  })

  const data = await response.json()

  return {
    statusCode: 200,
    body: JSON.stringify(data),
  }
}

3단계: 프론트엔드에서 호출

const response = await fetch('/.netlify/functions/my-api')
const data = await response.json()

주의사항

🚨절대 하지 말 것

프론트엔드에 API 키 넣기

// 절대 금지!
const API_KEY = 'sk-1234567890' // 빌드 시 코드에 박힘
fetch(url, { headers: { Authorization: API_KEY } })

환경변수를 프론트엔드에서 사용

// 절대 금지! (Gatsby에서는 빌드 시 값이 주입됨)
const key = process.env.GATSBY_API_KEY // 브라우저에서 보임

.env 파일 Git에 업로드

# .gitignore에 반드시 추가!
.env
.env.local
.env.production

트러블슈팅

Q. “Netlify Functions가 실행 안 돼요”

  • netlify.toml에 functions 디렉토리 설정 확인
  • 함수 파일이 exports.handler 형태인지 확인

Q. “환경변수가 undefined로 나와요”

  • 로컬: .env 파일에 변수가 있는지 확인
  • 배포: Netlify 대시보드에 환경변수 설정했는지 확인
  • 재배포가 필요할 수 있음

Q. “CORS 에러가 나요”

  • Netlify Functions는 같은 도메인이라 CORS 문제 없어야 함
  • 외부 API 호출 시 해당 서비스의 CORS 정책 확인




이 접근의 아쉬운 점

1. 콜드 스타트 지연 (최대 2초)

Netlify Functions의 가장 큰 단점입니다.

콜드 스타트란?

한동안 호출이 없으면 함수가 “잠들고”, 다음 호출 시 “깨어나는” 시간이 필요합니다.

  • 첫 호출: 1~2초 지연
  • 이후 호출 (5분 이내): 즉시 응답

문제 상황:

  • 뉴스레터 구독 버튼 클릭 → 2초간 응답 없음
  • 사용자: “어? 안 먹혔나?” (재클릭 → 중복 요청)

해결책:

  • 로딩 스피너 표시 (“처리 중입니다…“)
  • 버튼 비활성화 (중복 클릭 방지)
  • 또는 Netlify Pro 플랜 ($19/월) → 콜드 스타트 0초

우리 선택:
무료 플랜 + 로딩 UI로 충분합니다. 하루 방문자 500~1000명 수준에서는 문제없습니다.


2. Netlify 종속성

Netlify가 문제 생기면?

  • 서비스 장애
  • 가격 정책 변경 (무료 플랜 축소)
  • 서비스 종료 (가능성 낮지만)

대응책:

  • Functions 코드를 표준 Node.js로 작성 (다른 플랫폼 이전 쉽게)
  • Vercel/AWS Lambda로 마이그레이션 준비 (1~2시간 작업)

실제 경험:
2년 운영 중 장애 없음. Netlify는 안정적입니다.


3. 복잡한 로직엔 부적합

Netlify Functions는 간단한 작업에 최적화되어 있습니다.

용도적합성이유
API 키 숨기기✅ 완벽단순 프록시
뉴스레터 구독✅ 완벽단순 POST 요청
블로그 통계 조회✅ 완벽단순 GET 요청
이미지 리사이징⚠️ 제한적메모리 제한 (1GB)
대용량 파일 처리❌ 부적합타임아웃 (10초)
웹소켓❌ 불가능단방향 요청만 지원

만약 복잡한 로직이 필요하다면?
AWS Lambda (타임아웃 15분, 메모리 10GB) 또는 Node.js 서버를 고려해야 합니다.


4. 디버깅이 어렵다

로컬 개발은 쉽지만, 배포 후 문제 발생 시:

  • Netlify 로그만 확인 가능 (제한적)
  • 실시간 디버깅 불가능
  • 에러 추적이 서버만큼 세밀하지 않음

해결책:

  • 충분한 로깅 추가 (console.log)
  • Sentry 같은 에러 모니터링 도구 연동
  • 로컬에서 netlify dev로 충분히 테스트

5. 결국 “충분히 좋은 해결”을 선택했다

Netlify Functions는 완벽하지 않습니다. 하지만:

  • ✅ 개인 블로그 규모에는 완벽
  • ✅ 서버 운영 부담 제로
  • ✅ 비용 거의 무료
  • ✅ 배포가 너무 쉬움 (Git push)

만약 하루 방문자 1만 명 이상이라면?
그때는 AWS Lambda나 Node.js 서버를 고민해야 할 겁니다.

지금은 이 정도로 충분합니다.




시스템 점검 체크리스트

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

  • 환경변수 분리: API 키가 .env 파일과 Netlify 대시보드에만 있고, 코드에는 없는가?
  • .gitignore 설정: .env 파일이 Git에 업로드되지 않도록 설정했는가?
  • 에러 핸들링: Functions에서 try-catch로 에러를 잡고 적절한 statusCode를 반환하는가?
  • CORS 설정: 필요한 경우 headers에 Access-Control-Allow-Origin을 설정했는가?
  • 콜드 스타트 대응: 로딩 UI와 버튼 비활성화로 중복 클릭을 방지했는가?



결론

일반 웹과 정적 블로그의 가장 큰 차이는 인증의 주체입니다.

graph LR
    subgraph 일반 웹
        A1[사용자 인증] --> B1[JWT 토큰]
        B1 --> C1[백엔드 API 호출]
    end

    subgraph 정적 블로그
        A2[서버 인증] --> B2[환경변수]
        B2 --> C2[서버리스 함수 호출]
    end

    style A1 fill:#e3f2fd
    style A2 fill:#fff3e0
  • 일반 웹: “당신이 누구인지”를 증명해야 함 (JWT 토큰)
  • 정적 블로그: “서버가 신뢰할 수 있는지”만 중요 (환경변수)

왜 Netlify Functions가 필요한가?

솔직히 처음에는 “그냥 Node.js 서버 하나 띄울까?” 고민했습니다. 익숙하니까요.

하지만 Netlify Functions를 선택한 이유는 명확했습니다:

  1. 운영 부담 제로: 서버 모니터링, 배포 파이프라인, 스케일링… 다 Netlify가 해줌
  2. 비용 효율: 월 100원 미만 (EC2 t3.micro도 월 2만원+)
  3. 자동 스케일링: 갑자기 트래픽이 늘어도 걱정 없음
  4. 빠른 개발: 함수 하나 만들면 바로 배포

“최고의 서버는 운영하지 않는 서버”라는 말이 실감났습니다.


로그인이 없는 이유

일반 웹은 특정 사용자에게만 서비스를 제공합니다. 내 데이터를 보려면 내가 누군지 증명해야 하죠.

하지만 블로그는 누구나 접속 가능한 공개 콘텐츠입니다. 굳이 “당신이 누구인지” 알 필요가 없습니다. 대신 서버가 신뢰할 수 있는 요청인지만 중요합니다.

그래서 사용자 인증(JWT) 대신 서버 인증(환경변수)을 사용하는 거죠.


마무리 통찰

7년간 일반 웹만 개발하다가 정적 블로그로 넘어오니 신선했습니다.

“로그인 없이 어떻게 보안을 유지하지?”라는 질문에 대한 답은 간단했습니다. 관점을 바꾸면 됩니다.

일반 웹에서는 “사용자가 누구인지”가 중요합니다. 하지만 공개 블로그에서는 “어떤 요청이 신뢰할 수 있는지”가 중요합니다. Netlify Functions는 그 신뢰를 환경변수로 보장해주고, 저는 서버 운영 부담 없이 백엔드 기능을 사용할 수 있게 됐습니다.

“왜 Netlify Functions를 써야 하나요?”

이제 이 질문에 자신있게 답할 수 있습니다. API 키를 안전하게 보호하면서 서버 운영 부담 없이 백엔드 기능을 사용하기 위해서입니다.





참고 :

https://docs.netlify.com/functions/overview/
https://developers.google.com/analytics/devguides/reporting/data/v1
https://supabase.com/docs/guides/api




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


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

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