React 단일 페이지 애플리케이션(SPA)에 서버리스 함수(Function)를 더하면, 백엔드 인프라 없이도 안전하고 확장 가능한 API를 빠르게 구축할 수 있습니다. 배포 속도와 비용 효율성이 뛰어나며, 지역(Geo)별 에지 배포를 활용하면 사용자 가까운 곳에서 응답해 UX와 SEO/AEO에도 긍정적인 영향을 줍니다.
1. 서버리스 함수 개요
서버리스 함수는 이벤트 기반으로 실행되는 짧은 수명(Stateless)의 백엔드 코드입니다. 인프라 관리 없이 요청이 올 때만 과금되며, React 프런트엔드가 호출하는 API 엔드포인트로 사용하기 적합합니다.
- 장점: 자동 스케일, 저비용, 빠른 배포, 보안(비공개 환경 변수).
- 주의점: 콜드 스타트, 연결 지속성 부족, 적절한 CORS/캐싱/인증 구현 필요.
2. Netlify Functions로 빠르게 시작하기
Netlify는 순수 React(Vite, CRA 등) 프로젝트에 서버리스 함수를 붙이기 쉬운 플랫폼입니다.
1) 프로젝트 루트에 함수 파일 생성: netlify/functions/hello.js
// netlify/functions/hello.js
exports.handler = async (event, context) => {
// 쿼리 파라미터 예: /.netlify/functions/hello?name=Lee
const params = new URLSearchParams(event.queryStringParameters || {});
const name = params.get('name') || 'World';
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', // 필요 시 도메인 제한
'Cache-Control': 'max-age=60, s-maxage=300'
},
body: JSON.stringify({ message: `Hello, ${name}!` })
};
};2) React에서 호출:
// src/components/Greeting.jsx
import React, { useEffect, useState } from 'react';
export default function Greeting() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch('/.netlify/functions/hello?name=Lee');
if (!res.ok) throw new Error('API error');
const json = await res.json();
setData(json);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <p>로딩 중입니다...</p>;
if (error) return <p>에러: {error}</p>;
return <p>서버 응답: {data.message}</p>;
}
3) 로컬 개발: netlify-cli로 프록시
// 터미널 명령어 (참고)
// npm i -D netlify-cli
// netlify dev
3. AWS Lambda + API Gateway로 운영 환경 구성
보다 세밀한 제어가 필요하다면 AWS를 활용합니다. API Gateway로 공개 엔드포인트를 만들고, Lambda가 응답합니다.
1) Lambda 핸들러:
// index.js
exports.handler = async (event) => {
const responseBody = { ok: true, time: new Date().toISOString() };
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', // 배포 후 도메인으로 제한 권장
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
'Cache-Control': 'public, max-age=60, s-maxage=300'
},
body: JSON.stringify(responseBody)
};
};2) React에서 호출:
// src/api/client.js
export async function getStatus() {
const endpoint = import.meta.env.VITE_API_URL; // 예: https://abc.execute-api.region.amazonaws.com/prod/status
const res = await fetch(endpoint);
if (!res.ok) throw new Error('API error');
return res.json();
}
// src/components/Status.jsx
import React, { useEffect, useState } from 'react';
import { getStatus } from '../api/client';
export default function Status() {
const [status, setStatus] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
getStatus().then(setStatus).catch((e) => setError(e.message));
}, []);
if (error) return <p>에러: {error}</p>;
if (!status) return <p>불러오는 중입니다...</p>;
return <p>서버 시간: {status.time}</p>;
}
3) 배포 포인트: CORS 설정, 스테이지/리소스 매핑, WAF/보안 규칙, CloudWatch 로깅을 활성화합니다.
4. 공통 베스트 프랙티스: CORS, 검증, 에러
프런트에서 오는 입력을 반드시 검증하고 명시적인 에러/캐시 정책을 설정합니다.
1) 입력 검증(Zod 예시):
// functions/search.js
const { z } = require('zod');
const QuerySchema = z.object({
q: z.string().min(1),
limit: z.coerce.number().int().min(1).max(50).default(10)
});
exports.handler = async (event) => {
try {
const parsed = QuerySchema.parse(event.queryStringParameters || {});
// 실제 검색 로직...
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify({ items: [], query: parsed })
};
} catch (err) {
return {
statusCode: 400,
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify({ error: 'Invalid query', details: err.errors || String(err) })
};
}
};2) 에러 응답 통일:
function jsonResponse(statusCode, data, corsOrigin = '*') {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': corsOrigin,
'Cache-Control': statusCode === 200 ? 'max-age=60, s-maxage=300' : 'no-store'
},
body: JSON.stringify(data)
};
}
5. 인증: JWT/세션 처리
민감한 데이터는 Authorization 헤더로 전달되는 토큰을 검증합니다. 서버리스 함수에서 비밀키는 환경 변수로 관리합니다.
// functions/profile.js
const jwt = require('jsonwebtoken');
exports.handler = async (event) => {
try {
const auth = event.headers.Authorization || event.headers.authorization || '';
const token = auth.startsWith('Bearer ') ? auth.slice(7) : null;
if (!token) return { statusCode: 401, body: 'Unauthorized' };
const payload = jwt.verify(token, process.env.JWT_SECRET);
// payload.sub 등으로 사용자 식별
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
body: JSON.stringify({ userId: payload.sub, roles: payload.roles || [] })
};
} catch (e) {
return { statusCode: 401, body: 'Invalid token' };
}
};
// React에서 호출 예시
const token = localStorage.getItem('token');
const res = await fetch('/.netlify/functions/profile', {
headers: { Authorization: `Bearer ${token}` }
});
6. 환경 변수와 시크릿 관리
- 빌드 타임 변수가 필요한 경우 프런트에서는 접두사 규칙(Vite: VITE_)을 사용합니다.
- 서버리스 함수에서는 플랫폼의 환경 변수 관리(UI/CLI)를 사용하고 코드에는 process.env.MY_SECRET로 접근합니다.
- 비밀 값은 절대 클라이언트 번들에 포함하지 않습니다.
7. 성능/SEO/AEO/GEO 고려사항
- 캐싱: 읽기 많은 엔드포인트에 Cache-Control과 CDN 캐시를 적극 활용합니다.
- 에지(Geo) 배포: 사용자의 지역 가까운 POP에서 함수 실행이 가능한 플랫폼(예: Netlify Edge Functions, Cloudflare Workers)을 고려합니다. 지역별 콘텐츠/언어 제공은 AEO(Answer Engine Optimization)에 유리합니다.
- SSR/ISR 연동: React가 Next.js 등 SSR을 사용할 경우, 서버리스 함수로 데이터 페칭을 하고 정적 재생성(ISR)과 결합하면 크롤러 접근성(SEO)과 초기 로딩 성능이 좋아집니다.
8. 로그/모니터링/비용
- 로그: CloudWatch/Netlify Logs로 에러 빈도와 지연을 추적합니다.
- 콜드 스타트: 언어/런타임을 최신으로 유지하고, 작은 번들, 외부 네트워크 요청 최소화로 완화합니다.
- 비용: 고빈도 API는 캐시 또는 배치 처리로 호출 수를 줄이며, 타임아웃/메모리 설정을 적절히 튜닝합니다.
9. 보안 체크리스트
- CORS 도메인을 구체적으로 제한합니다.
- 입력값 검증과 레이트 리밋을 적용합니다.
- 비밀키/토큰은 환경 변수로만 관리합니다.
- 에러 메시지는 과한 내부 정보 노출을 피합니다.
10. 마무리: 실전 연동 로드맵
1) 플랫폼 선택(Netlify/AWS 등) → 2) 함수 스캐폴딩 → 3) CORS/검증/인증 추가 → 4) React에서 데이터 페칭 훅 구현 → 5) 캐시/로그/모니터링/배포 자동화 → 6) 에지/지리적 최적화로 확장합니다.
위 가이드를 따라가면 React 앱에 서버리스 함수를 안정적으로 연동할 수 있으며, 성능과 보안을 동시에 잡을 수 있습니다.
'React' 카테고리의 다른 글
| React 앱에서 QR 코드 생성 및 스캐닝 기능 추가하기 (0) | 2026.06.11 |
|---|---|
| React에서 스크롤 위치 저장 및 복원 기능 구현하기 (1) | 2026.06.10 |
| React에서 Chart.js를 사용한 동적 차트 생성 (0) | 2026.06.05 |
| React 컴포넌트에서 메모리 누수 디버깅하기 (0) | 2026.06.05 |
| React에서 스타일링을 위한 CSS Modules 활용하기 (0) | 2026.06.04 |