Next.js 15+ Server Actions를 Cloudflare OpenNext/Workers로 배포할 때 트러블슈팅과 성능 최적화

Next.js 15와 Server Actions는 리액트 생태계에 큰 변화를 가져왔습니다. 하지만 이를 Vercel이 아닌 Cloudflare Workers와 같은 엣지 환경에서 구동하려면 여러 가지 기술적 난관에 부딪히게 됩니다. 다행히 OpenNext 덕분에 Next.js 앱을 Cloudflare에 배포하는 과정이 훨씬 수월해졌습니다.
이 글에서는 Next.js 15+ 환경에서 Server Actions를 Cloudflare OpenNext/Workers로 배포할 때 겪을 수 있는 트러블슈팅 포인트와 성능 최적화 방법을 상세히 다룹니다.
1. OpenNext와 Cloudflare Workers 구조 이해하기
Vercel은 Node.js 환경(또는 자체 Edge 런타임)을 제공하지만, Cloudflare Workers는 V8 Isolate 기반의 고유한 런타임을 사용합니다. 따라서 Node.js 내장 모듈(예: fs, path, crypto)에 직접적으로 의존하는 코드는 에러를 발생시킬 수 있습니다.
OpenNext는 Next.js 빌드 출력물을 Cloudflare Workers가 이해할 수 있는 형태로 변환(Adapter)해줍니다. 하지만 Server Actions의 경우 폼 데이터를 파싱하고 서버에서 실행된 결과를 반환하는 과정에서 런타임 차이로 인한 미묘한 버그가 발생하기 쉽습니다.
2. Server Actions 배포 시 흔히 겪는 에러와 해결책
2-1. FormData 파싱 에러 (Multipart/form-data)
Server Actions에서 파일 업로드를 처리할 때 multipart/form-data 파싱 에러가 종종 발생합니다. Cloudflare Workers의 메모리 제한(128MB)과 요청 크기 제한(일반적으로 100MB)에 걸릴 수 있습니다.
해결책: 큰 파일 업로드는 Server Actions를 통해 직접 전송하기보다는, Cloudflare R2의 Presigned URL을 발급받아 클라이언트에서 직접 업로드(Direct Upload)하는 방식을 사용해야 합니다.
// app/actions.ts
'use server'
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({
region: "auto",
endpoint: process.env.R2_ENDPOINT,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});
export async function getUploadUrl(filename: string, contentType: string) {
const command = new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME,
Key: filename,
ContentType: contentType,
});
// 엣지에서 60초간 유효한 URL 생성
const signedUrl = await getSignedUrl(s3, command, { expiresIn: 60 });
return signedUrl;
}
2-2. Node.js 의존성 에러 (Bcrypt 등)
유저 인증을 위해 bcrypt와 같은 Node.js 네이티브 바인딩 라이브러리를 Server Actions에서 사용하면 Workers 환경에서 크래시가 발생합니다.
해결책:
Web Crypto API를 지원하는 bcryptjs (순수 JS 구현체) 또는 @node-rs/argon2의 WASM 버전, cf-workers-hash 등을 사용해야 합니다.
2-3. 데이터베이스 커넥션 풀링 고갈
Server Actions는 요청마다 실행됩니다. PostgreSQL 등과 직접 연결(TCP)하는 경우 연결이 고갈될 수 있습니다.
해결책: Cloudflare Hyperdrive를 사용해 커넥션 풀링을 관리하거나, HTTP 기반의 데이터베이스(예: Prisma Accelerate, Neon Serverless, Supabase REST)를 사용하세요.
3. 성능 최적화 팁
3-1. Edge Runtime 명시하기
Server Action이 특정 라우트에 묶여 있다면, 해당 라우트가 명시적으로 엣지 런타임을 사용하도록 선언하면 성능이 향상됩니다. OpenNext가 이를 더 효율적으로 처리합니다.
export const runtime = 'edge';
3-2. Revalidate를 활용한 캐시 무효화
Server Actions 작업 완료 후 revalidatePath나 revalidateTag를 사용할 때, OpenNext의 KV 기반 캐싱 동작 방식을 이해해야 합니다. Cloudflare KV는 결과적 일관성(Eventual Consistency)을 가지므로 갱신이 최대 60초까지 지연될 수 있습니다.
즉각적인 캐시 무효화가 필요하다면 Workers KV 대신 Cloudflare D1이나 외부 Redis (Upstash 등)를 Next.js의 Custom Cache Handler로 설정하는 것을 고려해보세요.
요약 (Summary Box)
💡 Cloudflare에서 Server Actions를 위한 핵심 체크리스트
- 큰 파일은 Server Actions 대신 R2 Presigned URL로 클라이언트에서 직접 업로드하세요.
- Node.js 네이티브 API(
fs, C++ Addons) 사용을 피하고 Web API나 순수 JS 패키지로 교체하세요.- DB 연결 시 Hyperdrive나 HTTP 기반 Serverless DB를 활용하세요.
revalidatePath사용 시 Cloudflare Cache/KV의 결과적 일관성을 인지하고 설계하세요.
OpenNext와 Cloudflare Workers의 조합은 Next.js 생태계를 가장 저렴하고 빠르게 글로벌로 서비스할 수 있는 환상적인 인프라입니다. 초기의 작은 설정들만 주의한다면 강력한 Server Actions의 이점을 엣지에서 완벽하게 누릴 수 있습니다.