한 줄 아이디어를 입력하면 분장 → 장면 → 키프레임 → 동영상까지 4단계로 풀어 만드는 React 19 + Electron 기반 데스크톱·웹 영상 생성 스튜디오. 5월 26일 TrendShift 1위.
이 레포가 만들려는 결과물을 알아야 한다.
중국 모바일 인터넷에서 폭발한 콘텐츠 장르가 短剧(duǎnjù, 단편극)이다. 회당 1~3분, 총 80~100회 분량의 세로형 미니드라마인데, 2024년 기준 시장 규모가 500억 위안(약 9.5조 원)을 넘어 박스오피스를 추월할 정도로 커졌다. 짧고 자극적인 훅 → 빠른 갈등 → 다음화 후크로 도파민을 직격하는 구성이 특징이다.
「만화극(漫剧)」은 그 변형이다. 실사 촬영 대신 일러스트 + 가벼운 모션 + 더빙으로 만들어, 배우·로케이션 비용 없이 한 사람이 회사처럼 양산할 수 있다. deep-printfilm은 바로 이 「혼자서 회사처럼 양산」을 가능하게 하는 도구다.
577★, TrendShift 1위, 5/26 기준.
Sora·Veo 같은 Text-to-Video 모델은 한 컷씩 잘 만들지만, 캐릭터가 다음 컷에서 다른 얼굴로 나오는 문제가 고질병이다. deep-printfilm은 「먼저 분장 사진을 정해두고, 그 사진을 모든 컷에 참조시키는」 식으로 일관성 문제를 영화 제작 방식으로 풀었다.
경쟁 제품과 비교해 보면 차별점이 더 선명하다.
RunwayML이나 Pika는 5초 짜리 영상 클립을 잘 뽑는다. 하지만 80화짜리 短剧를 만들려면 캐릭터 이름·분장·소품 관리, 장면별 키프레임 관리, 렌더 로그 같은 「제작 관리」 도구가 필요하다. 이 부분은 외부 스프레드시트로 따로 해야 한다.
ComfyUI는 노드를 연결해 어떤 파이프라인이든 짤 수 있다. 대신 매번 노드 그래프를 설계해야 해서, 「오늘 5편 찍자」는 양산 모드엔 안 맞는다. deep-printfilm은 4단계 정해진 흐름만 따르도록 강제해 의사결정 피로를 줄였다.
Canva·CapCut의 AI 영상은 BGM·자막 잘린 「릴스용 단편」에 강하다. 1~3분짜리 「이야기」가 있는 영상은 한계가 명확하다. deep-printfilm은 스크립트 분해 → 컷 분할 → 키프레임 일관성을 묶어 「드라마」를 만들 수 있게 했다.
TrendShift 1위에 오른 건 결국 「중국 短剧 시장」이라는 거대한 실수요와 「Sora/Veo 출시 직후의 워크플로 공백」이 정확히 맞물린 타이밍 때문이다. 별 567개 중 대다수가 최근 30일에 찍혔다.
package.json·Dockerfile·vite.config.ts에서 그대로 뽑은 사실들.
이 프로젝트는 풀스택 백엔드가 없다. 자체 서버 없이 브라우저에서 모든 것을 처리하고, AI 호출만 외부 API로 보내는 「서버리스 데스크톱 앱」 구조다.
React 19는 2025년 안정화된 최신 메이저로, useActionState·서버 컴포넌트·자동 배치 처리(automatic batching) 개선이 들어있다. Vite 6은 빌드를 esbuild로 처리해 1~2초 만에 핫리로드된다.
의존성이 의외로 가볍다: react, react-dom, lucide-react(아이콘), jszip(자산 zip 다운로드) — 사실상 이 4개가 다다. Tailwind CSS도 없이 「공업풍 다크 UI」를 만든 점이 특이하다(아마 모든 스타일을 인라인 또는 CSS Module로 처리).
Electron은 Chromium + Node.js를 한 묶음으로 만든 「데스크톱 위장 브라우저」다. VSCode·Slack·Figma 데스크톱이 모두 Electron이다. deep-printfilm은 같은 React 코드로 웹·Windows·macOS 세 곳에 동시 출시한다.
특이점: electron/main.cjs에 「내장 HTTP 서버」를 띄워 데스크톱 안에서 /api-proxy를 직접 처리한다. 일반 Electron 앱이 fetch를 그대로 쓰는 것과 다른 패턴.
Electron은 「웹사이트에 창틀을 끼워 데스크톱 앱처럼 만드는 액자」다. 액자 안 그림(React UI)은 그대로지만, 액자 자체(Chromium + Node)가 OS와 대화해 「파일 저장」·「알림」 같은 데스크톱 기능을 켜준다.
# Dockerfile 핵심 (multi-stage build)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev && \
npm install --no-save vite@^6.2.0 ... # Electron은 일부러 제외
COPY . .
RUN npm run build # → dist/ 생성
FROM nginx:alpine # 2단계: 가벼운 Nginx
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
이 Dockerfile에는 흥미로운 디테일이 있다. 주석에 「Electron은 의존성 트리가 너무 커서 npm이 멈춘 것처럼 보임 → 일부러 제외」라고 적혀 있다. 도커 이미지 안에서는 웹용 빌드만 만들어 Nginx에 얹는다.
이게 deep-printfilm의 가장 큰 「판매 포인트」이자 「의존 포인트」다. OpenAI·Anthropic·Google에 각각 키를 받지 않고, GitCC라는 중국 AI 게이트웨이 1개에만 키를 등록하면 GPT·Claude·Gemini·Sora·Veo를 OpenAI 호환 API로 부를 수 있다.
GitCC API(api.gitcc.com)는 OpenAI·Google·xAI에 대한 직접 접근이 막힌 중국 사용자에게 「우회 + 결제」를 한 번에 해주는 서비스다. 한국에서 쓰면 결제·환불·약관이 모두 중국어이고, 모델 라인업도 GitCC가 정한 풀에 묶인다. 직접 OpenAI Sora·Google Veo를 쓸 수 있는 국가에선 굳이 거칠 필요가 없다.
vite.config.ts의 /api-proxy → http://api.gitcc.com 부분과 모델 설정 UI에서 baseUrl을 바꾸면 된다. OpenRouter, Together AI, LiteLLM 같은 OpenAI 호환 라우터를 꽂으면 한국에서도 똑같이 동작한다.
백엔드 DB가 없다. 프로젝트·캐릭터·장면·키프레임·생성 로그 전부 브라우저의 IndexedDB에 저장된다. 장점은 「설치·서버 운영 비용 0」, 단점은 「브라우저 데이터 지우면 모든 작품이 날아감」이라는 점.
「Script-to-Asset-to-Keyframe」 4단계 파이프라인의 실체.
전체 흐름을 그림으로 그려보면 이렇다.
┌─────────────────────────────────────────────────────────────────┐
│ 사용자 (브라우저 또는 Electron 창) │
└───────────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ React UI │──────▶│ services/ │────────▶│ /api-proxy │
│ (App.tsx, │ │ geminiService│ fetch │ (Vite dev │
│ components/)│ │ ~98KB │ │ 또는 Nginx │
└──────┬───────┘ │ │ │ reverse │
│ │ + adapters/ │ │ proxy) │
│ 상태 저장 │ + modelReg.. │ └──────┬───────┘
▼ │ + storageSvc │ │
┌──────────────┐ │ + exportSvc │ ▼
│ IndexedDB │◀──────│ + renderLog │ ┌─────────────────┐
│ (브라우저) │ │ │ │ api.gitcc.com │
│ │ └──────────────┘ │ (OpenAI 호환 │
│ - projects │ │ 게이트웨이) │
│ - assets │ │ │
│ - keyframes │ │ GPT / Claude / │
│ - videos │ │ Gemini / │
│ - renderLogs │ │ Sora / Veo │
└──────────────┘ └─────────────────┘
─── 데스크톱 모드일 때만 ───
▲
┌───────────────────────┐ │
│ electron/main.cjs │ Express + http-proxy- │
│ + 내장 Express 서버 │──middleware 로 /api-proxy ─┘
│ (Electron 메인 프로세스) │ 처리 (CORS 우회)
└───────────────────────┘
types.ts에 있는 ProjectState가 「프로젝트 1개의 우주 전체」다. 이 객체 하나에 스크립트, 캐릭터, 장면, 컷, 키프레임, 비디오 인터벌, 렌더 로그가 모두 들어 있다. UI는 stage: 'script' | 'assets' | 'director' | 'export' | 'prompts' 필드만 보고 어떤 화면을 그릴지 결정한다.
// types.ts에서 발췌
export interface ProjectState {
id: string;
title: string;
stage: 'script' | 'assets' | 'director' | 'export' | 'prompts';
rawScript: string; // 사용자가 처음 입력한 문장
scriptData: ScriptData | null; // AI가 파싱한 구조 (캐릭터·장면·컷)
shots: Shot[]; // 컷 배열 (keyframes + interval 포함)
renderLogs: RenderLog[]; // 생성 기록 (모델·토큰·비용·실패 이유)
}
한 영화 제작실에 있는 「대본 + 콘티 + 스토리보드 + 일정표 + 영수증 묶음」을 종이 클립 하나로 묶어둔 폴더를 생각하면 된다. ProjectState 객체가 그 폴더고, 4개 Stage UI는 「대본 페이지만 보이는 책장」, 「스토리보드만 보이는 책장」 같은 식이다. 같은 폴더를 다른 각도에서 본다.
이 프로젝트가 「Sora를 그대로 쓰는」 일반 영상 생성기와 갈라지는 지점이 여기다. 한 컷(Shot)에 무조건 「시작 프레임(start keyframe) + 끝 프레임(end keyframe)」을 두고, 이 두 그림을 보간(interpolation)해 영상을 만든다.
키프레임은 「캐릭터 분장 사진」을 참조해 만든다. 그래서 5컷이 지나도 같은 얼굴이 유지된다. 일반 T2V(Text-to-Video)는 매번 새 얼굴이 나온다.
「칼이 왼쪽 위에서 오른쪽 아래로 떨어지는 컷」을 만들고 싶을 때, 두 키프레임에서 칼의 위치를 직접 그려둘 수 있다. 모델은 「둘 사이를 어떻게든 채워라」만 하면 되니 통제가 쉽다.
영상 생성은 1컷당 분 단위로 걸리고 비싸다. 키프레임만 먼저 정해두고 「이대로 가도 OK」를 확인한 뒤에 영상을 돌리면, 잘못된 컷 통째로 다시 만드는 일이 줄어든다.
레포 전체에서 가장 비대한 단일 파일이 services/geminiService.ts다. 무려 약 98KB(약 3,000줄 추정). 이름은 「Gemini」지만 실제로는 OpenAI 호환 채팅·이미지·비디오 API를 한 군데서 호출하는 「범용 AI 어댑터」다.
실무 코드 리뷰라면 「분할하라」는 지적이 나올 크기다. 모델별 어댑터, 프롬프트 템플릿, 응답 파싱, 폴링 로직, 에러 분류가 한 파일에 다 들어 있을 가능성이 높다. services/adapters/ 폴더에는 이미 chatAdapter.ts · imageAdapter.ts · videoAdapter.ts · index.ts 4개 파일이 분리돼 있어, 리팩토링이 진행 중임을 보여준다.
처음부터 깔끔하게 분리하려고 추상화하면 변경 비용이 폭증한다. 한 파일에 다 몰아넣고, 트래픽이 모이는 패턴이 보이면 그때 폴더로 쪼개는 것이 「프로토타입 → 제품」 단계에서 가장 빠르다. deep-printfilm은 정확히 이 단계에 와 있다.
services/modelRegistry.ts(15KB)는 「어떤 모델이 있고, 어떤 endpoint·파라미터를 받는지」를 데이터로 정의해둔 파일이다. types.ts의 VideoModelConfig에 보이는 'veo' | 'sora-2' | 'veo_3_1_t2v_fast_landscape' | 'veo_3_1_i2v_s_fast_fl_portrait' 같은 모델 ID가 이 파일에서 관리된다.
새로운 모델이 GitCC 게이트웨이에 추가될 때마다 코드를 거의 안 고치고 이 파일만 업데이트하면 UI에 자동으로 노출되는 구조 — 「Convention over Configuration」의 한 형태다.
35커밋, 거의 100% TypeScript.
deep-printfilm/
├── App.tsx # 최상위 React 컴포넌트, 라우팅·상태
├── index.tsx # ReactDOM.render 엔트리
├── index.html # Vite가 처리하는 HTML 셸
├── package.json # 의존성 + Electron build 설정
├── vite.config.ts # /api-proxy 리버스 프록시, alias
├── Dockerfile # multi-stage: node → nginx
├── docker-compose.yaml # 포트 3005, 단일 서비스
├── nginx.conf # 정적 호스팅 + API 프록시
├── tsconfig.json # TypeScript 옵션
├── types.ts # ★ 모든 도메인 타입 (한 파일 집중)
├── metadata.json # 앱 메타 (Electron 빌드용)
│
├── electron/ # 데스크톱 패키징
│ └── main.cjs # Express 내장 서버 + BrowserWindow
│
├── components/ # React UI (Stage별 분리)
│ ├── Dashboard.tsx # 프로젝트 목록
│ ├── Sidebar.tsx # 좌측 네비
│ ├── ModelConfig/ # 모델·API Key 설정 UI
│ ├── Onboarding/ # 첫 사용자 가이드
│ ├── StageScript/ # Phase 01 — 대본·분경
│ ├── StageAssets/ # Phase 02 — 캐릭터·장면
│ ├── StageDirector/ # Phase 03 — 키프레임·영상
│ ├── StageExport/ # Phase 04 — 미리보기·다운로드
│ └── StagePrompts/ # 자산 프롬프트 관리
│
├── services/ # 비즈니스 로직 (UI 없음)
│ ├── geminiService.ts # ★ 약 98KB AI 어댑터 본체
│ ├── modelRegistry.ts # 모델 카탈로그
│ ├── modelConfigService.ts# 사용자 모델 설정 영속화
│ ├── modelService.ts # 모델 호출 헬퍼
│ ├── storageService.ts # IndexedDB CRUD
│ ├── assetLibraryService.ts # 캐릭터/장면 자산 관리
│ ├── renderLogService.ts # 생성 기록 영속화
│ ├── exportService.ts # JSZip으로 자산 패키징
│ ├── soraVideoResolve.ts # Sora 비동기 결과 폴링
│ ├── videoHttpErrors.ts # 영상 API 에러 분류
│ ├── mockData.ts # 데모 데이터
│ └── adapters/ # chatAdapter.ts · imageAdapter.ts · videoAdapter.ts · index.ts (모델별 어댑터 분리 완료)
│
├── images/ # README 스크린샷
├── public/ # 정적 자산
└── types/ # 추가 타입 모듈
주목할 패턴 1: components/Stage* 폴더 — 한 「Stage」가 한 폴더다. Phase 01~04 + 자산 관리 = 5개 Stage. 같은 Stage 안의 작은 컴포넌트들은 폴더에 모이고, 다른 Stage가 그것에 의존하지 못한다. 「수직 슬라이스(vertical slice)」 구조.
주목할 패턴 2: services/ vs components/ 분리 — UI는 components/, 비즈니스 로직은 services/. React 컴포넌트가 fetch 하지 않고, 항상 service 함수를 부른다. 이 덕분에 모델·API가 바뀌어도 UI 코드는 안 건드린다.
주목할 패턴 3: types.ts 단일 파일 — TypeScript 타입을 한 파일에 모두 모았다. 도메인이 크지 않을 때(이 경우 200줄 정도) 가장 빨리 「전체 모델을 머리에 그리는」 방법. 도메인이 더 커지면 도메인별 파일로 쪼개야 한다.
기술별로 정리. 각 항목 끝에 「직접 해볼 것」.
react@^19.2.0을 실전 투입한 오픈소스가 아직 흔치 않다. App.tsx에서 어떤 19 신기능을 쓰는지 살펴보면 좋다. useActionState로 양식 처리 / useOptimistic으로 「영상 생성 중」 낙관적 UI / 새 use() 훅으로 Promise 직접 읽기 같은 패턴이 있는지 체크.
해볼 것: App.tsx와 components/StageDirector를 클론 받아 git log로 「React 19 마이그레이션」 커밋이 있는지 찾아보고, 18 → 19로 바꾼 코드 diff를 학습.
일반 Electron 튜토리얼은 mainWindow.loadFile('index.html')로 정적 파일을 띄운다. deep-printfilm은 한 단계 더 나가 express + http-proxy-middleware를 내장해 데스크톱 안에서 API 프록시까지 처리한다. 로컬에 풀스택 환경을 한 .exe로 배포하는 패턴이다.
해볼 것: electron/main.cjs를 열어 BrowserWindow 옵션, contextIsolation, preload script를 확인. 「Express 서버를 왜 메인 프로세스에 띄웠는가」를 직접 추적.
Dockerfile 한 파일에 「빌드용 무거운 이미지(node:20-alpine ~200MB)」와 「실행용 가벼운 이미지(nginx:alpine ~25MB)」를 둘 다 쓰고, 빌드 결과물만 후자에 복사한다. --from=builder가 그 마법이다.
해볼 것: 자기 프로젝트의 Dockerfile을 single-stage → multi-stage로 리팩토링. docker images로 전후 크기 비교.
vite.config.ts의 server.proxy 설정으로 개발 모드에서 /api-proxy/* → http://api.gitcc.com/*로 보낸다. 브라우저는 자기 자신(localhost)에 요청한 줄 아니까 CORS 오류가 안 난다. 풀스택 백엔드 없이 API 호출하는 가장 빠른 패턴.
해볼 것: 자기 SPA에서 「외부 API를 직접 fetch했더니 CORS 막힘」 경험이 있다면, Vite·Next.js의 dev proxy로 풀어보기.
services/storageService.ts(8KB)를 읽으면 IndexedDB CRUD 패턴을 배울 수 있다. idb 라이브러리도 안 쓰고 네이티브 API를 직접 사용한 것으로 보인다(의존성에 없음). Promise로 감싸는 헬퍼, version migration, transaction 패턴이 포인트.
해볼 것: storageService.ts를 그대로 떼어내, 「오프라인 작동하는 to-do 앱」에 이식.
OpenAI·Anthropic·Google·Sora·Veo는 요청 스펙이 다 다르다(특히 동영상). geminiService.ts + modelRegistry.ts + modelService.ts 3개가 만드는 「공통 인터페이스 → 모델별 어댑터」 분리 방식을 학습하면, LangChain 같은 무거운 라이브러리 없이도 멀티 LLM 앱을 만들 수 있다.
해볼 것: 자기 챗봇에 OpenRouter 1개만 붙여본 적이 있다면, 같은 패턴으로 「로컬 Ollama + OpenAI + Claude」를 한 번에 부르도록 확장.
이미지·텍스트와 달리 영상 모델은 결과가 30초~5분 걸린다. 따라서 「제출 → ID 받고 → 폴링 → 완료되면 URL 반환」하는 비동기 패턴이 필수. soraVideoResolve.ts(2.8KB)가 그 핵심.
해볼 것: 한 번이라도 직접 Sora·Runway API를 호출해본 적 없다면, 이 파일을 모방해 작은 폴링 헬퍼를 만들어보고 setTimeout vs Web Workers의 차이를 비교.
로컬 모델이 없으니 의외로 가볍다.
이 프로젝트는 모든 AI 추론을 외부 API에 위임하므로, 로컬에서는 브라우저만 잘 돌면 된다. ComfyUI·Stable Diffusion처럼 24GB VRAM이 필요하지 않다.
크롬·엣지·Safari 최신 버전. RAM 4GB 정도면 작동. IndexedDB에 영상 자산이 쌓이므로 디스크 여유 공간은 프로젝트당 1~5GB 권장.
RAM 8GB+, SSD 권장. 키프레임 이미지가 Base64로 IndexedDB에 저장되어 한 프로젝트가 수백 MB까지 커진다. HDD에서는 IndexedDB 쿼리가 체감 느려진다.
Node.js 20+ (Dockerfile이 node:20-alpine 기준), npm 10+, Git. Windows 빌드는 Windows에서, macOS 빌드는 macOS에서 해야 함(electron-builder 제약). Docker로 웹 버전만 띄우려면 docker compose up -d 한 줄.
Sora·Veo 영상 1컷이 수십 센트~몇 달러. 80화 단편극을 만들면 수백~수천 달러까지 갈 수 있다. 로컬 GPU가 0이어도, 신용카드 한도가 사실상의 「시스템 요구사항」이다.
난이도별 5개. 위에서 아래로 갈수록 어렵다.
git clone → npm install → npm run dev → localhost:3000 접속. GitCC API Key가 없으면 데모 모드로 화면만 둘러볼 수 있는지 확인. README는 영어·중국어 둘 다 제공.
docker compose up -d --build로 3005 포트 컨테이너 띄우기. Cloudflare Tunnel·ngrok로 외부 공개 URL을 임시 발급해 다른 사람에게 데모. nginx.conf의 /api-proxy 설정이 어떻게 GitCC로 라우팅되는지 추적.
vite.config.ts의 /api-proxy target을 https://openrouter.ai로 바꾸고, modelRegistry.ts에 OpenRouter 모델 ID를 추가. 「가장 작은 모델 1개라도 실제 호출 → 응답」 확인. 한국에서 합법적·합리적으로 동작하는 변형판을 만드는 과제.
storageService.ts에 「로그인 시 Supabase로 동기화」 기능 추가. 한 사용자가 여러 디바이스에서 같은 프로젝트를 이어 작업할 수 있게. 충돌 해결(CRDT 또는 last-write-wins) 전략을 직접 설계해 보기. 이 과제만 끝내도 React + IndexedDB + Supabase 풀스택을 한 번에 익힘.
services/adapters/ 폴더에 「로컬 ComfyUI 서버를 부르는」 어댑터를 추가. 이미지 생성은 ComfyUI에 위임하고, 영상은 Wan2(또는 Hunyuan-Video) 같은 로컬 오픈소스 영상 모델로 대체. API 비용 0, 인터넷 없이 작동하는 deep-printfilm 포크를 만드는 과제. 진짜 GPU(VRAM 16GB+) 필요.
8주 코스. 매주 한 영역.
공식 문서(react.dev)의 「Hooks Reference」를 useState부터 use까지 순서대로. 각 훅마다 「작은 codepen 1개」씩 만들기. TypeScript는 「제네릭 + 유틸리티 타입(Partial, Pick, Omit)」까지.
ESM vs CommonJS, esbuild·SWC의 Rust·Go 기반 컴파일러 차이. Vite의 dev server가 「번들링 없이 ESM 그대로」 보내는 원리. HMR(Hot Module Replacement) 디버깅 경험.
BrowserWindow·preload·contextIsolation·IPC(메인↔렌더러). electron-builder로 코드 사이닝(인증서) 없이 일단 unsigned 빌드 만들어보기. 자기 React 토이 프로젝트를 데스크톱 앱으로 포장.
Dockerfile 직접 작성, multi-stage 적용. docker-compose로 「프론트 + DB + Redis」 3개 컨테이너 한 번에 실행. docker network ls·docker volume ls로 격리 개념 익히기.
deep-printfilm의 nginx.conf를 통째로 읽고 한 줄씩 주석 달기. try_files $uri /index.html로 SPA 라우팅 처리, proxy_pass로 백엔드 위임, gzip·cache 헤더 설정.
idb 라이브러리(by Jake Archibald) 학습. 「오프라인 작동하는 메모 앱」을 만들고, 온라인이 되면 동기화. Service Worker로 PWA(Progressive Web App) 만들기까지.
OpenAI Chat Completions, DALL-E 3, Sora API를 직접 호출하면서 「공통 인터페이스 추상화」 연습. LangChain 없이도 30~50줄 어댑터로 충분함을 체득. 비동기 폴링·재시도·토큰 카운팅.
같은 deep-printfilm을 (1) Vercel에 정적 배포 (2) Docker로 자기 NAS에 띄우기 (3) Cloudflare Tunnel로 외부 공개 — 3가지 방식 비교. 비용·관리 부담의 trade-off 체감.
레포에 등장하는 용어 모음.
Shot 인터페이스가 정확히 이 단위.veo_3_1_t2v_fast_landscape·veo_3_1_i2v_s_fast_fl_landscape가 각각 그 약자. I2V가 캐릭터 일관성에 훨씬 유리.RenderLog가 그것. 영상 제작은 비용·시간이 크기 때문에 추적이 실무에서 필수./api-proxy 설정을 자기 프로젝트에 그대로 가져온다. 「CORS 우회」 문제를 한 줄로 푸는 가장 깔끔한 방법.