클라우드플레어 Pages로 정적 파일 서버 만들기 (에셋 서버)

파일 업로드용 무료 서버

블로그 개설 당시에 파일 업로드가 불가능한 블로그스팟을 위해 Cloudflare R2 저장소를 외부 미디어 서버로 만들었어요.

한동안 편하게 잘 쓰고 있었는데요, 에지 서버가 로스앤젤레스(LAX)로 연결되는 경우가 많았어요. 크게 느리진 않지만 그렇다고 빠르다기엔 미묘한... 그런 갑갑함이 있었죠.

그래서 뭐 좋은 거 없나 하고 살펴보던 중, 같은 회사의 Pages라는 서비스가 눈에 들어왔어요.

정적 사이트를 만드는 서비스인데요, CSS나 JS 파일을 올려서 정적 에셋 서버로 사용할 수도 있더라고요. 당연하게도 아이콘 같은 이미지 파일도 가능해요.

Pages 빠른 시작

클라우드플레어 로그인 한 뒤 계정 수준에서,

  1. 대시보드 > 컴퓨트 > Workers 및 Pages
  2. 애플리케이션 생성
  3. 하단의 'Pages를 배포하려고 하십니까? 시작' 클릭
  4. 'Git 리포지토리' vs '파일 끌어서 놓기' 중 끌어서 놓기 시작 (사용중 전환 불가)
  5. 프로젝트 이름 정하고 생성
  6. 폴더 끌어서 놓기
클라우드플레어 pages 빠르게 시작하기.

이러면 Pages 정적 서버가 만들어진 거고요, 끌어다 놓을 폴더는 해당 폴더의 루트가 서버의 루트가 되는 거예요.

새 버전을 업로드하면 자동으로 최신 배포 버전이 되고 기존 버전은 비활성화 되는데, 늘어나면 지저분하니까 안 쓰는 버전은 가끔 삭제해 주셔요. All deployments 목록 우측의 점 세개를 누르면 메뉴가 나와요.

같은 목록에서, 점 세개 옆의 Details는 해당 Pages 배포 버전의 상세 정보를 보여줘요. 눌러보면 업로드한 파일과 설정한 headers 및 redirects, functions의 내용을 볼 수 있어요.

Pages 서버 주소

서버 주소는 기본적으로 프로젝트 이름으로 정해져요 maid-server.pages.dev 이런 식이죠.

주소를 그대로 써도 되고요, 도메인을 따로 연결해 줄 수도 있는데,

  1. 만들어진 Pages 프로젝트 클릭
  2. 상단의 사용자 설정 도메인
  3. '사용자 설정 도메인 설정' 버튼 클릭
  4. 원하는 도메인 주소 입력

이렇게 하면 그 다음은 자동으로 진행돼요.

예를 들어 정적 에셋 서버 주로를 asset.maidvsai.com으로 하겠다, 그럼 해당 주소를 입력하고 계속하면 클라우드플레어가 DNS 설정이랑 Pages 연결을 자동으로 해 주는거죠. 이제부턴 asset.maidvsai.com으로도 접속할 수 있어요.

주의점

주의점이 몇가지 있어요.

  • 개별 파일 수정 불가, 폴더 단위 전체 업로드만 가능
  • 파일 1000개 제한
  • 단일 파일 25MB 제한

폴더를 끌어서 직접 업로드하는 방식은 이런 제한이 있어요. CLI를 사용하면 파일 수 20000개 까지 허용되지만 단일 파일 크기 제한은 동일하고요.

git 리포지토리와 연결하면 개별 파일 업데이트 가능한 걸로 아는데, 이건 저도 해 본적 없어서 정확히는 모르겠네요. 어차피 개인 블로거 수준에선 git 연결이 더 복잡할 것 같아서 직접 업로드를 선택했어요.

R2 저장소와 Pages의 장단점

R2 저장소와 비교해서 서로 장단점이 있는데요, Pages의 가장 큰 장점은 데이터 전송은 완전 무료라는 점이에요.

R2 저장소도 무료 범위가 넓지만 그래도 일정 요청 수를 넘어가면 요금이 붙는데, Pages는 요청 무제한에 트래픽도 무제한이거든요. 한 번에 만들 수 있는 프로젝트 1개, 한달에 빌드 가능한 수 500번, 그리고 맞춤 도에인 연결 수 100개 제한이 있지만 그 외는 비용 청구가 없어요. (2026-07 기준)

속도도 제법 빨라요. 대략 한달 정도 써 봤는데, 에지 서버가 언제나 인천(ICN)으로 붙더라고요. 이러면 응답 속도가 보통 30~100ms 정도가 나와서 지연 시간이 별로 안 느껴져요.

물론 접속하는 사람의 통신망에 따라 외국으로 나갈 가능서은 배제할 수 없고, 인천 서버로 연결된다고 무조건 빠르기만 한 건 아니에요. 하지만 로스앤젤레스(LAX)까지 태평양을 왕복하는 시간 보단 빠르죠.

기록적으로 느렸던 상황은 dns 조회가 520ms까지 늘어진 적이 있어요. 인천 서버에 붙었는데 말이에요. 특히 한동안 접속이 없다가 갑자기 접속해서 파일을 요청할 때 더 느린 것 같아요.

인천 서버로 연결되었지만 아주 느린 DNS 조회 시간.

아마 무료 계정은 인천(ICN) 서버에 연결되어도 처리 우선순위가 낮은게 아닐까요? 캐시 유지 시간도 짧다던가... 무료 봉사하는 단체가 아니니 어느 정도는 차별을 둘 것 같아요.

그리고 에셋 서버의 DNS 조회나 TTFB가 빨라도 결국 html 파일을 읽어들이는 것이 우선이니 완전히 광속 다운로드는 아니에요. 어쩔 수 없는 일이죠.

최근들어 개별 게시물의 html 파일도 인천(ICN) 에지로 연결되는 일이 많아져서 좀 빠르긴 한데요, 이건 언제 상황이 바뀔지 모르니 상수로 기대할 수는 없어요. 역시나 접속한 통신망에 따라 서버 위치가 갈릴 수도 있고요. 망 사용료 때문에 구조적으로 피할 수 없는 문제라... 해결책은 없네요.

자유로운 캐시 헤더 설정 가능

R2 저장소에 비해 편리한 점이 하나 더 있어요. Pages는 캐시헤더를 마음대로 설정할 수 있다는 점이에요. Pages 루트 디렉토리(폴더)에 확장자 없는 '_headers' 파일을 두고 그 안에서 설정하면 돼요. 일단 코드부터 보시죠.

https://:asset.maidvsai.com/*
  X-Robots-Tag: noindex
  
/*
  Access-Control-Allow-Origin: https://www.maidvsai.com

/component/*
  Cache-Control: public, max-age=86400, stale-with-revalidate=1209600
  Cloudflare-CDN-Cache-Control: max-age=86400, stale-with-revalidate=1209600

이 코드가 현재 제가 쓰고있는 에셋 서버의 _headers 파일 일부분이에요.

대략적으로 설명하면 asset.maidvsai.com은 색인하지 말고, www.maidvsai.com에서만 자산 사용을 허용하며(CORS), 특정 디렉토리별로 캐시 기간을 설정하고 있는 거예요. 여기서 다중 CORS 부분은 따로 섹션 만들어서 설명할게요.

제일 아래에 있는 /component/* 부분은 루트 아래의 component 디렉토리 내부에 적용하겠다는 뜻인데, 필요한 만큼 디렉토리를 추가해 주시면 돼요.

max-age는 캐시할 기간, stale-with-revalidate는 캐시 기간 지난 후 언제까지 기존 컨텐츠를 '한 번' 보여줄 것인지 초 단위로 적으셔요.

자주 쓰는 초-시간-일자 변환
시간
86400 24시간 하루
172800 48시간 이틀
259200 72시간 사흘
432000 120시간 닷새
604800 168시간 이레
1209600 336시간 14일
2592000 720시간 30일
5184000 1440시간 60일
7776000 2160시간 90일
15552000 4320시간 180일
31536000 8760시간 365일

Cloudflare-CDN-Cache-Control 항목은 에지 캐시 기간을 정하는 건데, 필요없으면 지우시고요. 적어두면 에지 캐시가 헤더에 지시된 기간만큼 유지돼요.

하지만 캐시 헤더는 보통은 필요없을 거예요. Cloudflare Pages에 올린 파일은 기본 캐시가 적용되고, 새 버전을 배포하면 자동으로 교체되거든요. (max-age:0, ETag)

반면, 사용자 지정 헤더로 캐시를 적용하면 자동 교체가 안 돼요. 캐시 기간을 길게 했을 경우 에지 캐시는 수작업으로 지워야 하고요, 방문자의 브라우저에 저장된 캐시는 max-age 기간동안 아예 건드릴 수도 없으니 필요한 곳에만 선택적으로 적용해야 해요.

* 경로 아래에 적는 [name]: [value]의 시작은 반드시 공백으로 들여쓰기 되어야 해요. 문법이 그래요.

리디렉션 및 워커 스크립트도 가능

같은 루트 디렉토리에 _redirects 라는 파일을 만들어서 리디렉션을 지시할 수도 있는데요, 우리는 에셋 서버로 쓸거니까 아직은 필요없을 거예요. 아예 정적 사이트를 만들 거라면 필요하겠죠.

functions라고 워커 기능을 가져와서 사용하는 것도 있는데요, 이것도 에셋 서버로 사용하는 지금은 필요없죠. 무료 워커 사용량을 공유하니 더더욱.

글 미리보기에서 Pages의 CORS 문제

Pages의 CORS 부분은 R2 저장소와 다르게 (루트 기준) 하나만 쓸 수 있어요. 와일드카드도 못쓰고요.

따로 디렉토리(폴더)를 만들어서 해당 경로에 별도의 Access-Control-Allow-Origin을 정해줄 수 있는데요, 폰트 파일은 그래도 사용이 안 되더라고요. CORS 오류가 발생해요.

미리보기의 스타일은 포기해도 되겠지만... 꼭 미리보기에서 폰트까지 깔끔하게 다 나오게 하고싶다면, Pages 프로젝트를 하나 더 만들어서 운영하는 게 깔끔해요.

처음 프로젝트 만든 것처럼 따로 Pages 서버 만들어 주시고 블로그스팟 테마 파일을 수정하여 body 태그 끝부분에 아래 자바스크립트를 붙여넣어 주셔요.

<script>
//<![CDATA[
const isPreview = window.location.href.includes('/blog-preview?');
if (isPreview) {
  const prvCSSMain = document.createElement('link');
  prvCSSMain.rel = 'stylesheet';
  prvCSSMain.crossorigin = 'anonymous';
  prvCSSMain.href = 'https://maid-project.pages.dev/style.min.css';
  const prvCSSFont = document.createElement('link');
  prvCSSFont.rel = 'stylesheet';
  prvCSSFont.href = 'https://maid-project.pages.dev/prv-font.css';
  document.head.appendChild(prvCSSMain);
  document.head.appendChild(prvCSSFont);
}
//]]>
</script>

maid-project 부분은 실제 주소로 바꿔주셔요. 물론 css 파일 경로 및 이름도 마찬가지고요.

메인 style의 경우 미리보기에서는 preload를 안 하니까 cors 경고가 안 뜨더라고요. 그래서 asset 서버에서 바로 불러와도 되는데, 어차피 서버 하나 새로만든 김에 보기좋게 싹 몰아넣었어요.


prv-font.css 내부는 대략 아래와 같아요.

@font-face {
    font-family: 'simple-line-icons';
    font-weight: normal;
    font-style: normal;
    font-variant: normal;
    font-display: swap;
    line-height: 1;
    src: url('Simple-Line-Icons.woff2') format('woff2');
}

폰트 패밀리, src 부분은 바꿔주시고요, 루트 디렉토리에 prv-font.css와 폰트 파일이 함께 있는 상황이니 이것도 필요에 따라 바꿔주셔요.


또, 새로 만든 Pages의 CORS 설정을 위해서 블로그스팟 미리보기의 서버 주소를 알아내야 하는데, 이건 관련글의 R2 저장소 글에서 CORS 항목을 참고하셔요.

R2 저장소의 CORS 설정과 다른점은 Pages 설정에서는 와일드카드 못 쓰니까 고유번호+난수를 전부 다 입력해야 한다는 점이네요.

난수의 경우 안 바뀌니까 걱정하지 마시고요. 저도 처음엔 수시로 바뀔 줄 알았는데 몇 달이 지나도 안 바뀌더라고요.

작업 폴더 분리를 위한 배치 명령

Pages가 업로드할 때는 편해요. 그냥 폴더를 잡아다 쭈욱~ 당겨서 놓으면 되거든요. '내가 이 파일을 업로드 했던가?' 이런 고민 필요없어요.

하지만 폴더 째로 올리다보니 그 안에 이런저런 작업 파일이 있으면 곤란하죠. 업로드 시간도 더 걸릴 것이고, 보안 문제도 있으니까요.

결국 작업 폴더와 업로드용 폴더를 별도로 구분해야 하는데, 작업 후에 '이 파일이 쓰는 파일인가...', '이걸 복사 했었나?' 고민하면서 파일 관리하면 머리가 깨져요. 그래서 이 부분을 간단한 자동화로 처리하고 있어요.

윈도우 기준으로 배치파일을 만들어서 쓰면 되는데요,

copy Asset\font\*.woff2 Pages-Maid-Asset\font\
copy Asset\Icon\* Pages-Maid-Asset\icon\
copy Asset\Profile\* Pages-Maid-Asset\profile\

copy Component\*.min.css Pages-Maid-Asset\component\
copy Component\*.min.js Pages-Maid-Asset\component\

@echo.
@echo Asset Copied
@echo.

copy Component\style.min.css Pages-Maid-Prv\
copy Asset\font\*.woff2 Pages-Maid-Prv\

@echo.
@echo Preview Asset Copied
@echo.

@echo Work End
@echo.

@pause

위 내용을 메모장에 붙여넣은 뒤 bat 파일로 저장하셔요. 물론, 경로 및 파일명은 바꾸시고요.

Asset, Component는 원본 폴더, Pages-Maid-Asset과 Pages-Maid-Prv 이 두 폴더는 복사될 목적지인데, 전부 bat 파일과 같은 레벨에 있어야해요.

부모 폴더 아래에 bat 파일이 있고, 같은 부모 아래에 원본과 복사 대상 폴더가 전부 있어야 한다는 뜻이에요.

제 PC의 로컬 폴더 및 파일 구조를 보면 아래와 같은 모습인데요, 이 구조를 그대로 쓰거나 본인 PC의 상황에 맞춰 위에 있는 bat 파일의 코드를 수정하시면 돼요.

  부모 폴더
    └ Asset
        └ font
        └ icon
        └ profile
    └ Component
        └ *.css
        └ *.min.css
        └ *.js
        └ *.min.js
    └ Pages-Maid-Asset
        └ _headers
    └ Pages-Maid-Prv
        └ _headers
    └ copy.bat
로컬 에셋 파일 복사 명령.
배치 파일 실행하면 자동으로 복사돼요

참고 자료