SvelteKit을 Cloudflare에 배포할 때 adapter-cloudflare를 이해하는 법


title: "SvelteKit을 Cloudflare에 배포할 때 adapter-cloudflare를 이해하는 법"
description: "Workers와 Pages의 배포 경로, 바인딩, platform API, 로컬 검증을 SvelteKit·Cloudflare 공식 문서 기준으로 정리한다."
slug: "sveltekit-cloudflare-adapter-2025"
tags: "SvelteKit, Cloudflare, Cloudflare Workers, Cloudflare Pages, adapter-cloudflare, Wrangler, serverless, web development"

SvelteKit을 Cloudflare에 올릴 때 @sveltejs/adapter-cloudflare는 단순히 호스팅 서비스를 고르는 스위치가 아니다. SvelteKit 빌드 결과를 Cloudflare의 실행 모델에 맞게 만들고, 서버 라우트와 정적 자산을 어떤 방식으로 전달할지 연결하는 어댑터다. 현재 SvelteKit 공식 문서는 이 어댑터가 Cloudflare Workers Static Assets와 Cloudflare Pages를 대상으로 모든 SvelteKit 기능을 지원한다고 설명한다. 예전 adapter-cloudflare-workers는 Workers Sites용이며 이미 폐기(deprecated)됐으므로, 새 프로젝트에서 그 패키지를 따라 설치하는 것은 피하는 편이 맞다.

먼저 구분할 것: Workers와 Pages는 같은 설정이 아니다

둘 다 adapter-cloudflare를 쓸 수 있지만, 배포 과정과 산출물을 읽는 주체가 다르다. Cloudflare Workers에서는 프로젝트 루트의 Wrangler 설정이 핵심이다. SvelteKit 문서의 기본 예시는 main.svelte-kit/cloudflare/_worker.js로, assets.directory.svelte-kit/cloudflare로 둔다. 즉 동적 요청을 처리할 Worker 진입점과 정적 파일 묶음을 Wrangler가 함께 배포하도록 연결한다.

Cloudflare Pages를 선택하면 Pages의 Git 연동 빌드 설정에서 프레임워크 프리셋을 SvelteKit으로 지정하고, 빌드 명령은 npm run build 또는 vite build, 빌드 출력 디렉터리는 .svelte-kit/cloudflare로 지정한다. 같은 어댑터 출력물을 쓰지만, Pages에서는 어댑터가 _routes.json도 생성하며 routes.includeroutes.exclude는 Pages 전용 옵션이다. exclude에 들어간 정적 경로는 Function을 거치지 않아 더 빠르고 비용도 적을 수 있다. 다만 include/exclude 규칙의 합계는 100개까지라는 제한이 있으므로, 사전 렌더된 URL을 한 개씩 열거하는 대신 가능한 경우 경로 패턴을 검토해야 한다.

현재 Cloudflare Pages 공식 문서는 새 애플리케이션의 출발점으로 Workers를 권한다. Workers가 대부분의 Pages 사용 사례를 다루고 더 폭넓은 기능을 제공한다는 이유다. 이것은 Pages가 잘못됐다는 뜻이 아니라, 기존 Pages 프로젝트를 유지할지와 새 서비스를 어느 런타임에서 시작할지를 분리해 판단하라는 신호에 가깝다. 이미 Pages의 Git 기반 미리보기 흐름이 팀의 운영 방식이라면 Pages가 자연스러울 수 있다. 반대로 Worker 바인딩, Worker 중심 배포, 커스텀 런타임 제어가 요구된다면 Workers 문서를 기준으로 설정을 설계하는 편이 혼동이 적다.

어댑터가 맡는 일과 최소 설정

패키지를 개발 의존성으로 설치하고 svelte.config.jskit.adapter에 등록하는 것이 출발점이다. 기본 설정만으로도 가능하지만, config는 표준 이름이 아닌 Wrangler 설정 파일 경로가 있을 때 지정하는 옵션이며, platformProxy는 개발 중 에뮬레이션되는 platform.env 바인딩의 선호도를 조정한다. 환경별 설정을 코드에 하드코딩하는 옵션으로 오해하지 않는 것이 좋다. 실제 바인딩의 이름과 대상은 Wrangler 설정 및 Cloudflare 프로젝트 쪽에서 일치해야 한다.

기존 SvelteKit 프로젝트라면 최신 Wrangler는 Wrangler 설정 파일이 없는 경우 프레임워크를 감지해 설정 생성을 제안할 수 있다. SvelteKit의 경우 필요한 어댑터를 설치하고 wrangler.jsonc, 유용한 package script, .gitignore 항목 등을 만든다. 하지만 자동 감지는 ‘배포를 이해하지 않아도 된다’는 의미가 아니다. Cloudflare는 wrangler setup --dry-run으로 변경 예정 내용을 먼저 볼 수 있다고 안내한다. 기존 설정 파일이 있으면 자동 구성은 실행되지 않으며, 모노레포에서는 실행한 디렉터리만 분석하므로 워크스페이스 루트에만 의존성이 있을 때 감지가 실패할 수 있다.

바인딩과 platform: 비밀값과 런타임 리소스를 섞지 말기

Cloudflare의 env 객체에는 KV, Durable Objects 같은 프로젝트 바인딩이 들어간다. SvelteKit 어댑터는 이를 platform을 통해 hooks와 server endpoint에 전달하고, 같은 맥락에서 ctx, caches, cf도 제공한다. 따라서 Cloudflare 고유 리소스는 서버 코드에서 platform?.env를 통해 접근할 수 있다. TypeScript 프로젝트라면 src/app.d.tsApp.Platform에 실제 바인딩 타입을 선언해 코드와 배포 설정의 불일치를 빨리 발견하는 방식이 유용하다.

다만 일반 환경 변수에까지 무조건 platform.env를 쓰는 것은 권장되지 않는다. SvelteKit 공식 문서는 환경 변수에는 내장 $env 모듈을 우선 사용하라고 명시한다. 정리하면, Cloudflare 제품에 연결되는 KV·R2·DO 같은 런타임 바인딩은 platform의 책임이고, SvelteKit의 환경 변수 모듈은 별도의 도구다. 두 경로를 구분해야 로컬 개발, 타입 선언, 배포 설정을 읽는 사람이 의도를 파악하기 쉽다.

로컬에서 ‘화면이 열린다’보다 한 단계 더 검증하기

npm run dev로 화면을 보는 것은 첫 단계다. 이 어댑터를 직접 사용하면 개발·preview 모드에서 Cloudflare 특화 platform 값이 에뮬레이션되고, Wrangler 설정을 바탕으로 로컬 바인딩이 platform.env에 채워진다. Workers용 빌드 결과는 wrangler dev .svelte-kit/cloudflare/_worker.js, Pages용 빌드 결과는 wrangler pages dev .svelte-kit/cloudflare로 확인하라는 것이 SvelteKit 문서의 안내다. ‘Vite 개발 서버에서 성공’과 ‘어댑터가 만든 산출물이 Cloudflare 방식으로 실행’은 같은 검증이 아니다.

또한 로컬 바인딩은 기본적으로 로컬 시뮬레이션 리소스에 연결된다. Cloudflare는 wrangler dev와 Cloudflare Vite 플러그인에서 바인딩별 원격 연결도 지원한다고 설명한다. 예를 들어 실제 원격 데이터에 의존하는 경로를 점검할 때는 해당 바인딩만 원격으로 연결하는 선택지를 검토할 수 있다. 반면 wrangler dev --remote는 Worker 코드 전체가 Cloudflare 인프라에서 실행되고 바인딩도 원격 리소스를 사용한다. Cloudflare는 빠른 반복과 디버깅에는 보통 로컬 개발과 바인딩별 원격 연결을 권한다. 어느 모드를 썼는지 기록하지 않으면 로컬에서 안전하다고 믿었던 테스트가 실제 데이터를 읽거나 쓸 수 있다.

자주 생기는 배포 오해

첫째, /functions 디렉터리에 Pages Function을 추가하면 SvelteKit 서버 코드와 합쳐질 것이라는 생각이다. SvelteKit 공식 문서는 프로젝트 루트 /functions의 함수는 이 배포에 포함되지 않으며, 서버 기능은 SvelteKit server endpoint로 구현해야 하고 결과적으로 단일 _worker.js로 컴파일된다고 설명한다.

둘째, 루트의 _headers_redirects 파일이 모든 응답에 적용된다는 생각이다. 이 파일들은 이미지 같은 정적 자산 응답에는 쓸 수 있지만, SvelteKit이 동적으로 렌더링한 응답에는 적용되지 않는다. 동적 응답의 헤더와 리디렉션은 endpoint 또는 handle hook에서 반환해야 한다.

셋째, Node 서버에서 되던 파일 시스템 접근이 Worker에서도 그대로 된다는 생각이다. Cloudflare Workers에서는 fs를 쓸 수 없다. SvelteKit은 배포된 public asset 위치에서 파일을 가져오는 $app/serverread 함수 사용 또는 관련 라우트의 prerender를 대안으로 제시한다. 큰 서버 의존성을 무심코 가져오면 빌드된 서버가 하나의 파일로 묶인 뒤 Worker 크기 제한을 넘겨 Wrangler 배포가 실패할 수도 있다.

마지막으로 404 동작을 단순한 프런트엔드 라우팅 문제로 보는 경우다. Workers에서 자산 요청이 맞지 않을 때의 기본 동작, assets.not_found_handling, 그리고 Pages의 routes.exclude는 서로 다른 조건에서 결과에 영향을 준다. 어댑터의 fallback은 평문 404와 SPA fallback을 선택하는 옵션이지만, Workers의 자산 not-found 설정이 single-page-application이면 해당 fallback 옵션과 무관하게 SPA index.html이 렌더링된다. 배포 전에는 실제로 존재하지 않는 자산 URL, 서버 라우트 URL, prerender된 URL을 각각 요청해 기대한 상태 코드와 본문이 나오는지 확인하는 편이 안전하다.

adapter-cloudflare의 핵심은 ‘Cloudflare에 맞는 빌드를 만든다’는 데 있고, 성공적인 운영은 그 다음에 달려 있다. Workers와 Pages 중 어떤 배포 주체를 선택했는지, 바인딩이 로컬 시뮬레이션인지 원격 연결인지, 동적 응답의 책임이 어디에 있는지까지 명확히 해 두면 같은 설정 파일을 보며 추측하는 시간을 크게 줄일 수 있다.

대표 출처

https://svelte.dev/docs/kit/adapter-cloudflare
https://developers.cloudflare.com/workers/framework-guides/web-apps/sveltekit/

koen