정적 블로그에서 백엔드 통신하기 - 일반 웹과의 결정적 차이
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 키 노출 위험: 코드에 넣으면 전부 공개
- 로그인 없음: 사용자별 인증 불가능