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를 구성하면서 생각보다 많은 이슈를 만났다. 이 글은 그 삽질 과정과 최종 해결책을 정리한 기록이다.
Cloudflare 대시보드 → Workers & Pages → 우측 사이드바에서 Account ID를 복사한다.
GitHub 레포지토리 → Settings → Secrets and variables → Actions에서 두 개의 secret을 등록한다.
CLOUDFLARE_API_TOKEN: 위에서 발급받은 API 토큰CLOUDFLARE_ACCOUNT_ID: 위에서 확인한 Account ID최종적으로 완성된 .github/workflows/ci.yml의 deploy job이다. lint → test →
build를 거친 후, main 브랜치 push일 때만 배포가 실행된다.
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에 넘겨서 배포한다.
여기서부터가 본론이다. 이 워크플로우에 도달하기까지 겪은 에러들을 시간순으로 정리했다.
처음에 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에 추가해야 한다.
# cloudflare
.wrangler/
.dev.vars
이슈 2: cloudflare/wrangler-action의 workspace 프로토콜 에러
cloudflare/wrangler-action@v3을 사용했다.- 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를 직접 실행한다.
- 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로 추가한다.
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을 사용한 것이다.
// 변경 전 - 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을 추가한다.
// 변경 후
export const CONTENT_DIR = path.resolve(
import.meta.dirname ?? "",
"../../../content",
);
vinext deploy가 자동 생성한 wrangler.jsonc가 prettier 포맷과 맞지 않아서
CI의 format:check에서 실패했다.
[warn] apps/web/wrangler.jsonc
[warn] Code style issues found in the above file.
prettier --write를 실행한다.pnpm exec prettier --write apps/web/wrangler.jsonc
vinext deploy를 처음 실행하면 아래 파일들이 자동 생성된다. 어떤 것을
커밋하고 어떤 것을 무시해야 하는지 정리했다.
| 파일 | 커밋 여부 | 설명 |
|---|---|---|
vite.config.ts | O | Vite + Cloudflare 플러그인 설정 |
worker/index.ts | O | Worker 엔트리포인트 |
wrangler.jsonc | O | Wrangler 설정 (prettier 포맷 후 커밋) |
.wrangler/ | X | Wrangler 캐시 디렉토리 |
.dev.vars | X | 로컬 환경변수 (API 토큰 포함) |
dist/ | X | 빌드 산출물 (이미 gitignore) |
vinext deploy 대신 빌드와 배포를 분리한다 (vinext build → wrangler deploy)cloudflare/wrangler-action은 pnpm workspace와 충돌하니 직접 wrangler를 실행한다vite, @cloudflare/vite-plugin, wrangler를 devDependencies에 명시한다import.meta.dirname 등).dev.vars와 .wrangler/는 반드시 gitignore한다vinext는 아직 초기 단계라 CI/CD 관련 공식 문서가 부족한 편이다. 이 글이 같은 삽질을 하는 누군가에게 도움이 되길 바란다.