
본문 이미지를 나중에 넣는 게 아니라 — SEOJing 글쓰기.
SEOJing에서 새 글을 쓸 때 대표 이미지와 본문 이미지를 빼먹지 않도록, 블로그 맵과 글쓰기 파이프라인 안에 시각 판단 단계를 넣은 과정을 정리했다.
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 IDPost Q&A
vinext + GitHub Actions로 Cloudflare Workers 배포하기 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
최종적으로 완성된 .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
핵심 포인트는 빌드와 배포를 분리하는 것이다. 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 포맷 후 커밋) |
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 관련 공식 문서가 부족한 편이다. 이 글이 같은 삽질을 하는 누군가에게 도움이 되길 바란다.
SEOJing 글을 소셜용 영상으로 따로 소비시키는 게 아니라, 포스트 상단 요약과 블로그 유입 장치로 연결하기 위해 summaryVideo frontmatter와 Supertonic3 기반 요약 쇼츠 파이프라인을 붙인 과정을 정리했다.
.wrangler/| X |
| Wrangler 캐시 디렉토리 |
.dev.vars | X | 로컬 환경변수 (API 토큰 포함) |
dist/ | X | 빌드 산출물 (이미 gitignore) |