LogoSEO Jing
  • All Posts
  • SEO Jing
  • KD Team
  • CLab CoreTeam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

vinextCloudflare WorkersGitHub ActionsCI/CDDevOps

vinext + GitHub Actions로 Cloudflare Workers 배포하기

2026년 3월 16일·12분 읽기

vinext란?

vinext는 Cloudflare가 만든 Vite 기반 Next.js 대체 프레임워크다. Next.js의 API 표면(App Router, Pages Router, next/* 모듈)을 Vite 위에서 재구현했고, 빌드 속도 4.4배, 번들 크기 57% 감소를 자랑한다. 무엇보다 vinext deploy 한 줄이면 Cloudflare Workers에 배포할 수 있다는 점이 매력적이다.

하지만 실제로 GitHub Actions CI/CD를 구성하면서 생각보다 많은 이슈를 만났다. 이 글은 그 삽질 과정과 최종 해결책을 정리한 기록이다.

사전 준비

1. Cloudflare API Token 발급

Cloudflare 대시보드에서 API 토큰을 발급받아야 한다.
  • Cloudflare 대시보드 접속
  • 좌측 메뉴 → My Profile → API Tokens
  • "Create Token" → "Edit Cloudflare Workers" 템플릿 선택
  • 토큰 생성 후 복사
  • 2. Cloudflare Account ID 확인

    Cloudflare 대시보드 → Workers & Pages → 우측 사이드바에서 Account ID를 복사한다.

    3. GitHub Secrets 등록

    GitHub 레포지토리 → Settings → Secrets and variables → Actions에서 두 개의 secret을 등록한다.

    • CLOUDFLARE_API_TOKEN: 위에서 발급받은 API 토큰
    • CLOUDFLARE_ACCOUNT_ID: 위에서 확인한 Account ID

    GitHub Actions 워크플로우

    최종적으로 완성된 .github/workflows/ci.yml의 deploy job이다. lint → test → build를 거친 후, main 브랜치 push일 때만 배포가 실행된다.

    yaml
    deploy:
      name: Deploy
      runs-on: ubuntu-latest
      needs: [build]
      if: github.event_name == 'push' && github.ref == 'refs/heads/main'
      steps:
        - uses: actions/checkout@v4
    
        - uses: pnpm/action-setup@v4
    
        - uses: actions/setup-node@v4
          with:
            node-version: 22
            cache: "pnpm"
    
        - run: pnpm install --frozen-lockfile
        - run: pnpm build
    
        - name: Deploy to Cloudflare Workers
          run: pnpm --filter web exec wrangler deploy --config dist/server/wrangler.json
          env:
            CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
            CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
    

    핵심 포인트는 빌드와 배포를 분리하는 것이다. vinext build로 빌드하면 dist/server/wrangler.json이 생성되고, 이 설정 파일을 wrangler deploy --config에 넘겨서 배포한다.

    트러블슈팅 이슈 노트

    여기서부터가 본론이다. 이 워크플로우에 도달하기까지 겪은 에러들을 시간순으로 정리했다.

    이슈 1: .dev.vars는 로컬 전용이다

    처음에 Cloudflare API 토큰을 apps/web/.dev.vars에 넣었다.

    CLOUDFLARE_API_TOKEN=my-api-token-here
    

    그런데 배포 시 인식을 못했다. .dev.vars는 wrangler의 로컬 개발용 환경변수 파일이다. 배포할 때는 셸 환경변수나 CI의 secrets로 CLOUDFLARE_API_TOKEN을 주입해야 한다.

    그리고 .dev.vars에는 API 토큰이 들어있으므로 반드시 .gitignore에 추가해야 한다.

    gitignore
    # cloudflare
    .wrangler/
    .dev.vars
    

    이슈 2: cloudflare/wrangler-action의 workspace 프로토콜 에러

    처음에는 공식 cloudflare/wrangler-action@v3을 사용했다.
    yaml
    - name: Deploy to Cloudflare Workers
      uses: cloudflare/wrangler-action@v3
      with:
        apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
        workingDirectory: apps/web
        command: deploy --config dist/server/wrangler.json
    
    그런데 이런 에러가 발생했다.
    npm error code EUNSUPPORTEDPROTOCOL
    npm error Unsupported URL Type "workspace:": workspace:*
    

    wrangler-action이 내부적으로 npm i wrangler를 실행하는데, pnpm의 workspace:* 프로토콜을 npm이 이해하지 못해서 발생한 문제다.

    해결: action을 쓰지 않고, 이미 pnpm install로 설치된 wrangler를 직접 실행한다.

    yaml
    - name: Deploy to Cloudflare Workers
      run: pnpm --filter web exec wrangler deploy --config dist/server/wrangler.json
      env:
        CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
    

    이슈 3: vite, @cloudflare/vite-plugin, wrangler 모듈 미설치

    vinext deploy를 로컬에서 실행하면 vite.config.ts, worker/index.ts, wrangler.jsonc를 자동 생성한다. 이 파일들을 커밋해서 CI에서 빌드하면 이런 에러가 발생했다.

    Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'vite'
    Could not resolve '@cloudflare/vite-plugin' in vite.config.ts
    Command "wrangler" not found
    

    vinext가 내부적으로 vite를 번들하고 있어서 로컬에서는 동작하지만, 자동 생성된 vite.config.ts가 vite와 @cloudflare/vite-plugin을 직접 import하기 때문에 이 패키지들이 devDependencies에 명시적으로 있어야 한다.

    해결: 세 패키지를 모두 devDependency로 추가한다.

    bash
    pnpm --filter web add -D vite @cloudflare/vite-plugin wrangler
    

    이슈 4: import.meta.dirname이 Workers에서 undefined

    이게 가장 까다로운 이슈였다. 배포는 되는데 Worker가 시작할 때 크래시가 났다.

    Uncaught TypeError: The "paths[0]" argument must be of type string. Received undefined
      at null.<anonymous> (node-internal:validators:116:15) in validateString
      at null.<anonymous> (node-internal:internal_path:942:13) in resolve
      at null.<anonymous> (index.js:26607:24)
    

    원인은 shared/config/index.ts에서 import.meta.dirname을 사용한 것이다.

    ts
    // 변경 전 - Workers에서 import.meta.dirname은 undefined
    export const CONTENT_DIR = path.resolve(
      import.meta.dirname,
      "../../../content",
    );
    

    import.meta.dirname은 Node.js 21+에서 지원하는 기능인데, Cloudflare Workers 런타임에서는 undefined를 반환한다. path.resolve에 undefined가 들어가면서 TypeError가 발생한 것이다.

    해결: nullish coalescing으로 fallback을 추가한다.

    ts
    // 변경 후
    export const CONTENT_DIR = path.resolve(
      import.meta.dirname ?? "",
      "../../../content",
    );
    

    이슈 5: wrangler.jsonc prettier 포맷 에러

    vinext deploy가 자동 생성한 wrangler.jsonc가 prettier 포맷과 맞지 않아서 CI의 format:check에서 실패했다.

    [warn] apps/web/wrangler.jsonc
    [warn] Code style issues found in the above file.
    
    해결: 커밋 전에 prettier --write를 실행한다.
    bash
    pnpm exec prettier --write apps/web/wrangler.jsonc
    

    vinext deploy가 생성하는 파일들

    vinext deploy를 처음 실행하면 아래 파일들이 자동 생성된다. 어떤 것을 커밋하고 어떤 것을 무시해야 하는지 정리했다.

    파일커밋 여부설명
    vite.config.tsOVite + Cloudflare 플러그인 설정
    worker/index.tsOWorker 엔트리포인트
    wrangler.jsoncOWrangler 설정 (prettier 포맷 후 커밋)
    .wrangler/XWrangler 캐시 디렉토리
    .dev.varsX로컬 환경변수 (API 토큰 포함)
    dist/X빌드 산출물 (이미 gitignore)

    마무리

    정리하면, vinext + GitHub Actions 배포의 핵심은 이렇다.
    1. vinext deploy 대신 빌드와 배포를 분리한다 (vinext build → wrangler deploy)
    2. cloudflare/wrangler-action은 pnpm workspace와 충돌하니 직접 wrangler를 실행한다
    3. vite, @cloudflare/vite-plugin, wrangler를 devDependencies에 명시한다
    4. Workers 런타임의 Node.js 호환성 차이를 주의한다 (import.meta.dirname 등)
    5. .dev.vars와 .wrangler/는 반드시 gitignore한다

    vinext는 아직 초기 단계라 CI/CD 관련 공식 문서가 부족한 편이다. 이 글이 같은 삽질을 하는 누군가에게 도움이 되길 바란다.

    포스트 목록

    /SEOJing
    파일 11개, 폴더 1개
    Cloudflare Workers에서 fs 모듈이 안 되는 이유와 해결법모바일 웹에서 가로 모드를 강제하는 5가지 방법 — iOS Safari에서도 동작하는 코드 뷰어 만들기블로그 글을 PPT로 만들기 — DOM 클로닝 기반 프레젠테이션 모드100vh가 100%가 아닌 이유 — 모바일 뷰포트 단위 완전 정리Context로 퀴즈 컴포넌트를 만들다 막혀서 React.Children을 공부하게 된 이야기localStorage 읽기에서 하이드레이션 에러가 터지는 이유 useSyncExternalStore로 해결useEffect cleanup과 의존성 배열 — 실전 버그 사례로 이해하는 생애주기vinext + GitHub Actions로 Cloudflare Workers 배포하기vinext 오픈소스 기여기: 한국어 slug가 RSC에서 이슈를 일으킨 이유RSC 환경에서 WebAssembly가 차단되는 이유 — Shiki에서 rehype-prism-plus로vinext는 왜 빠를까? — SSR, Vite, Edge, 그리고 Web Vitals까지