포스팅이 늘어날 때 GitHub Actions 빌드 타임아웃 해결한 Astro 정적 생성 최적화 가이드

Astro는 “가장 빠른 웹사이트를 만드는 프레임워크”라는 타이틀에 걸맞게, 클라이언트 측 자바스크립트를 최소화하는 섬(Island) 아키텍처를 기반으로 놀라운 성능을 보여줍니다. 하지만 서비스가 성장하고 콘텐츠(Markdown, MDX, Headless CMS 데이터)가 수백, 수천 개로 늘어나기 시작하면 로컬 환경과 CI/CD 파이프라인의 빌드 속도 차이에서 오는 이질감을 마주하게 됩니다.

특히 GitHub Actions의 무료 호스팅 러너(Ubuntu 기준 2-core CPU, 7GB RAM) 환경에서는 로컬 머신(M1/M2 Mac 등)보다 컴퓨팅 자원이 훨씬 제한적입니다. 포스팅 개수가 늘어남에 따라 빌드 시간이 선형적으로 증가하다가, 어느 순간 GitHub Actions의 빌드 타임아웃(기본 360분 제한 또는 팀 내 자체 설정한 CI 데드라인 제한)에 걸려 배포가 실패하는 임계점을 맞이하게 됩니다.

이 글에서는 포스팅 급증으로 인해 GitHub Actions 빌드 지연 및 타임아웃 문제를 겪고 있는 개발자를 위해, Astro의 빌드 메커니즘을 파헤치고 실전에서 즉시 적용 가능한 정적 생성(SSG) 최적화 전략을 단계별로 제안합니다.


1. 빌드가 느려지는 원인 분석: 로컬과 CI 환경의 하드웨어 차이

최적화를 시작하기 전에 왜 로컬에서는 10~20초 만에 끝나던 빌드가 GitHub Actions에만 가면 수 분 이상 걸리는지 이해해야 합니다.

비교 항목 로컬 개발 환경 (예: Apple M2 Pro) GitHub Actions 기본 러너 (Ubuntu)
CPU 코어 수 10 ~ 12 코어 (고성능 코어 포함) 2 코어 (vCPU)
메모리(RAM) 16GB ~ 32GB 7GB
디스크 I/O 고속 NVMe SSD 가상화 환경의 가상 디스크 (I/O 병목 발생)
캐시(Cache) node_modules 상시 보존 매 워크플로우 실행 시 초기화 (별도 설정 필요)

Astro 빌드 프로세스는 크게 세 단계로 나뉩니다.

  1. 콘텐츠 수집 및 파싱: Markdown/MDX 파일 읽기, Frontmatter 파싱, AST 생성.
  2. 에셋 최적화: astro:assets를 통한 이미지 리사이징, 포맷 변환(WebP/AVIF), 압축 (CPU 집약적).
  3. HTML 정적 생성: 파일 쓰기 및 라우팅 매핑 (I/O 집약적).

이 중 2번(에셋 최적화)과 3번(HTML 생성) 단계는 CPU 멀티코어 성능과 디스크 쓰기 속도에 극도로 의존합니다. 하드웨어 스펙이 낮은 GitHub Actions 환경에서 아무런 최적화 없이 빌드를 돌리면, 포스팅 수가 늘어날수록 빌드 시간이 기하급수적으로 늘어날 수밖에 없습니다.


2. GitHub Actions 캐시 전략 극대화하기

가장 먼저 제어해야 할 것은 중복 작업의 제거입니다. GitHub Actions는 매번 깨끗한 가상머신 상태에서 시작하므로, 이전 빌드에서 완료한 작업(패키지 다운로드, 이미지 변환 결과물 등)을 캐싱하는 것이 최적화의 첫걸음입니다.

Astro는 이미지 최적화 캐시와 콘텐츠 레이어(Content Layer) 캐시를 node_modules/.astro 경로에 저장합니다. 이를 GitHub Actions의 actions/cache와 연동해야 합니다.

GitHub Actions Workflow YAML 설정 예시

아래 설정은 pnpm 패키지 매니저를 기준으로, 의존성 패키지와 Astro의 빌드 캐시 디렉터리를 모두 보존하는 모범 사례 워크플로우입니다.

name: Deploy Astro Site

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install pnpm
        uses: pnpm/action-setup@v3
        with:
          version: 8

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      # Astro 이미지 최적화 캐시 및 Content Layer 캐시 복원/저장
      - name: Cache Astro build artifacts
        uses: actions/cache@v4
        with:
          path: node_modules/.astro
          key: ${{ runner.os }}-astro-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-astro-${{ hashFiles('pnpm-lock.yaml') }}-
            ${{ runner.os }}-astro-

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build Astro site
        run: pnpm build

왜 node_modules/.astro만 캐싱하면 되는가?

Astro의 이미지 최적화 캐시와 콘텐츠 레이어(Content Layer) 결과물은 node_modules/.astro 경로에 저장됩니다. 반면 프로젝트 루트의 .astro 디렉터리는 주로 IDE 자동완성을 위한 TypeScript 타입 정의 파일(.astro/types.d.ts)이 위치하는 곳으로, 빌드 시점에 매번 빠르게 재생성되므로 캐싱 대상에 넣을 필요가 없습니다. 오히려 불필요하게 캐싱하면 GitHub Actions의 캐시 용량 제한(리포지토리당 10GB)만 낭비하게 됩니다. node_modules/.astro만 캐싱해도 이미 최적화가 완료된 이미지 자산과 콘텐츠 레이어 결과물이 다음 빌드 때 재사용되어 빌드 시간을 단축할 수 있습니다.


3. 에셋 최적화 프로세스 커스텀 및 외부 서비스 이관

Astro의 내장 이미지 서비스(astro:assets)는 기본적으로 sharp 라이브러리를 사용해 서버 사이드에서 직접 이미지를 포맷팅합니다. 로컬에서는 눈 깜짝할 사이에 끝나지만, GitHub Actions 러너의 2-core CPU 환경에서 수백 장의 고해상도 이미지를 변환하는 작업은 가혹한 병목을 유발합니다.

이 문제를 해결하기 위해 두 가지 트레이드오프 전략을 취할 수 있습니다.

대안 A: 빌드 시 이미지 최적화 일시 비활성화 (개발/스테이징 한정)

만약 프로덕션 배포가 아닌 단순 PR 검증용 빌드라면, 굳이 고 무거운 이미지 변환 과정을 거칠 필요가 없습니다. 환경 변수를 통해 빌드 시 이미지 최적화 단계를 스킵하거나 품질 조정을 할 수 있도록 설정을 우회합니다.

astro.config.mjs 파일에서 다음과 같이 분기 처리를 구현합니다.

import { defineConfig } from 'astro/config';

const isCI = !!process.env.GITHUB_ACTIONS; // GitHub Actions가 자동으로 설정하는 표준 환경 변수

export default defineConfig({
  image: {
    // CI 환경에서는 무거운 AVIF 포맷 변환을 피하고 기본 포맷을 유지하여 CPU 부하 감소
    service: {
      entrypoint: 'astro/assets/services/sharp',
      config: {
        limitInputPixels: isCI ? 2000000 : true, // 입력 이미지 크기 제한으로 메모리 고갈 방지
      },
    },
  },
});

대안 B: 외부 이미지 CDN(Cloudinary, Unsplash 등) 활용

가장 이상적인 해결책은 빌드 머신의 리소스를 쓰지 않고, 이미지 최적화의 책임을 외부 전문 CDN으로 넘기는 것입니다. Astro에 이미지 경로만 전달하고, 쿼리 파라미터를 통해 CDN이 실시간으로 리사이징하도록 처리하면 빌드 타임의 이미지 변환 연산 비용이 0초로 수렴하게 됩니다.


4. Markdown 및 MDX 파싱 연산 줄이기 (Shiki 하이라이터 제어)

Astro 프로젝트 빌드가 느려질 때 의외로 간과하기 쉬운 주범이 바로 코드 블록 구문 강조(Syntax Highlighting) 도구인 Shiki입니다. Shiki는 매우 정교하고 아름다운 하이라이팅을 제공하지만, VS Code와 동일한 테마 엔진을 사용하기 때문에 수많은 코드 블록이 포함된 개발 블로그 빌드 시 엄청난 CPU 연산을 요구합니다.

최적화 조치: Shiki를 Prism으로 대체하거나 단일 테마 지정

무거운 Shiki 대신 속도가 빠른 Prism(RegExp 기반)으로 대체하거나, Shiki의 다중 테마 설정을 단일 테마로 줄여야 합니다.

astro.config.mjs 설정 변경 예시:

import { defineConfig } from 'astro/config';

export default defineConfig({
  markdown: {
    shikiConfig: {
      theme: 'one-dark-pro', // 듀얼 테마(Dark/Light) 설정은 로딩 및 파싱 속도를 2배 지연시키므로 단일 테마 권장
      wrap: true,
    },
  },
});

주의할 점은, Shiki는 기본적으로 마크다운 코드 블록에서 실제로 사용된 언어만 동적으로 지연 로딩(Lazy Loading)하기 때문에 langs 옵션으로 언어를 미리 제한해도 빌드 속도가 체감될 만큼 개선되지는 않습니다. 오히려 langs에 언어 목록을 직접 지정하면 Astro 4.x 이후 버전에서는 기본 내장 언어를 확장하는 것이 아니라 덮어써 버리므로, 목록에 없는 언어(예: python, rust)로 작성된 코드 블록은 문법 강조가 아예 동작하지 않게 됩니다. langs 옵션은 건드리지 말고 테마 단일화만 적용하는 것이 안전합니다.


5. 데이터 패칭 병렬화 (Promise.all 활용)

많은 입문자가 getStaticPaths() 내부나 컴포넌트 최상단에서 외부 API 혹은 로컬 콘텐츠 데이터를 호출할 때 직렬로 await를 나열하는 실수를 범합니다.

// ❌ 속도가 느려지는 직렬 처리 방식
const posts = await getCollection('blog');
const authors = await getCollection('authors');
const categories = await getCollection('categories');

이 방식은 각 호출이 끝날 때까지 다음 호출이 차단되어 대기 시간이 누적됩니다. 다음과 같이 비동기 병렬 처리를 통해 네트워크 및 디스크 I/O 대기 시간을 최소화해야 합니다.

//  속도가 극대화되는 병렬 처리 방식
const [posts, authors, categories] = await Promise.all([
  getCollection('blog'),
  getCollection('authors'),
  getCollection('categories')
]);

6. 빌드 성능을 측정하는 방법

최적화 효과는 프로젝트의 포스팅 수, 이미지 개수와 용량, 코드 블록 비중에 따라 크게 달라지므로 일반화된 수치를 그대로 신뢰하기보다는 자신의 프로젝트에서 직접 측정하는 것이 정확합니다. Astro는 astro build --verbose 플래그로 각 빌드 단계(콘텐츠 수집, 에셋 최적화, HTML 생성)별 소요 시간을 출력해 주므로, 최적화 적용 전후로 로컬과 GitHub Actions 양쪽에서 이 로그를 비교하면 어떤 단계가 실제 병목인지 확인할 수 있습니다. GitHub Actions에서는 각 스텝의 실행 시간이 Actions 실행 로그에 자동으로 기록되므로 별도 계측 없이도 캐시 적용 전후의 전체 빌드 시간을 비교할 수 있습니다.

캐싱 전략과 Shiki 테마 단일화, 그리고 이미지 처리 제한 설정을 함께 적용하면 하드웨어 자원이 열악한 CI 환경에서도 타임아웃 위험을 구조적으로 낮출 수 있습니다.


7. 배포 안정성을 유지하기 위한 CI/CD 체크리스트

향후 콘텐츠가 수천 개 규모로 더 늘어날 경우를 대비해, 빌드 파이프라인의 안전망으로 아래 체크리스트를 유지보수 가이드라인으로 삼는 것을 추천합니다.


Astro의 정적 사이트 빌드 속도를 개선하는 작업은 단순히 대기 시간을 몇 분 줄이는 것에 그치지 않습니다. 이는 배포 피드백 루프를 단축시켜 개발자 경험(DX)을 향상시키고, GitHub Actions의 분당 사용 과금을 절약하며, 예기치 못한 빌드 중단으로 사이트가 정상적으로 업데이트되지 않는 프로덕션 장애를 예방하는 가장 확실한 인프라 최적화 조치입니다.

서비스 규모와 하드웨어 환경의 한계를 정확히 파악하고, 불필요한 연산을 캐싱과 병렬화로 영리하게 우회하는 것. 그것이 바로 시간이 지나도 변함없이 가볍고 빠른 Astro 서비스를 유지하는 비결입니다.