[일지]

블로그 Astro 마이그레이션

Gatsby를 나와서 Astro로 옮기게 된 사연과 과정과 소감

최초 게시
2025년 11월 29일 03시
키워드
Astro, 마이그레이션,
후기

배경

전에, 블로그 코어Gatsby 프레임워크로 만들었다. 게시글은 별도 레포에 MDX로 작성했다. 어느 날, Markdown 주석 기능을 적용하고 싶었다. 거기서도 remark로 Markdown을 처리하니까, 적당한 플러그인을 끼우면 될 거라 생각했다. remark-gfm을 최신 버전으로 끼워넣었지만 실패했다. Gatsby에서 ESM을 부분적으로만 지원해서다.1 그래서 remark 플러그인을 ESM으로 전환되기 직전 버전인 remark-gfm@1.0.0remark-footnotes@3.0.0으로 따로 넣어야 했다. remark-footnotesremark-gfm으로 합쳐지면서 개발이 중단된 패키지이기도 하다.2

그때부터, 언제 빌드가 불가해질지 모른다는 불안감이 들었다.3 그때야 당장 쓸 수는 있으니까, 그리고 글을 얼마나 자주 쓸지도 모르니까 그대로 방치했다. 이제는 개발 서버를 실행하거나 빌드를 할 때마다 이미지 처리가 왔다 갔다 하는 상황까지 왔다. 글을 쓸 소재가 있더라도 제대로 배포가 가능할지 의심스러웠다. 일단 블로그 코어를 살려 보기로 했다.


어디로 탈출하나?

Gatsby를 대신할 프레임워크를 몇 가지 떠올렸다.

  • Next.js: 이전에 Gatsby와 함께 마지막까지 고민했던 후보이다. export 스크립트로 정적 사이트 생성이 가능하다. 자료를 찾아 보니, 게시글을 가져오고 정적 사이트 생성을 세팅하는 과정에서 손이 좀 가는 것 같았다.
  • React Router (Remix): 한 가지 모드만 존재하던 시절에 몇 번 써 본 적이 있다. 이제는 3가지 모드가 있다.4 내 상황이라면 ‘프레임워크 모드’를 선택하라고 한다. 공식 문서에서 답을 정해 주긴 했지만 선택지가 너무 많다.

이쯤 와서, React 기반의 SSG가 꼭 필요할까?’ 하는 생각이 들었다. 브라우저 단에서 DOM을 수정해야 하는 기능은 내 블로그에 아직 없다. 방문자 대부분은 필요한 글 하나만 읽고 나갈 것이다. 렌더가 된 HTML 코드와 프레임워크 코어와 페이지에 들어가는 데이터를 (또) 받고, 그게 일치하는지 확인하는 동작은... 내 블로그에서는 오버스펙이다. React 외의 대안도 떠올려 봤다.

  • Vite + 개별 페이지 빌드 스크립트 직접 작성: 손이 너무 많이 간다. 많은 걸 내가 원하는 대로 할 수 있지만, 그 많은 걸 내가 직접 해야 한다. 아두이노로 풀 와이어링 커스텀 키보드를 만든다면 이런 느낌일까?
  • Astro: 그 손이 많이 가는 걸 해 준다. 프레임워크에서 제시하는 패턴에 맞춰서 콘텐츠를 끌어오고 잘 처리하면, 내가 원하는 구조로 페이지를 구성할 수 있다. 페이지를 이루는 요소를 컴포넌트 단위로 작성할 수 있기도 하다. 페이지 로드 퍼포먼스도 좋다고 한다.

Astro로 탈출해 보기로 했다.


진행 과정

Astro에서 제공하는 마이그레이션 가이드에 따라서 진행했다.5 아래 순서로 진행했다.

  1. 디렉터리 구조 정리 및 기초 세팅
  2. 게시글 끌어오기: 콘텐츠 컬렉션 세팅
  3. 컴포넌트 변환 (React/Gatsby → Astro)
  4. GitHub Actions Workflow 수정

1. 디렉터리 구조 정리 및 기초 세팅

일단 디렉터리 구조를 Astro에 맞게 변경했다.6

📦 blog
├── 📂 public — 변형 없이 그대로 사용하는 리소스
├── 📂 src
│   ├── 📂 assets — 일반적인 리소스
│   ├── 📂 components — Astro 컴포넌트 등
│   ├── 📂 data — 콘텐츠 컬렉션으로 가져올 데이터
│   │   └── 📦 blog-post — 게시글 레포를 clone한 디렉터리
│   ├── 📂 layouts — 페이지에 적용할 레이아웃 컴포넌트
│   ├── 📂 pages — [필수] 파일 기반 라우팅을 위한 디렉터리
│   ├── 📂 styles — 스타일시트
│   ├── 📂 types — 타입 정의 등
│   ├── 📂 utils — 공통 유틸리티
│   └── 📄 content.config.ts — 콘텐츠 컬렉션 정의
├── 📄 astro.config.mjs — Astro 설정 파일
├── 📄 package.json
├── 📄 pnpm-lock.yaml
├── 📄 prettier.config.mjs
├── 📄 README.md
└── 📄 tsconfig.json

프로젝트를 새로 생성하는 게 아니고 기존 프로젝트를 옮기는 작업이었기 때문에 약간의 수작업이 들어갔다.

  1. 마이그레이션을 위한 작업 브랜치를 생성한다.
  2. 기존 src 디렉터리 이름을 src-old로 변경하고, Gatsby 관련 설정 파일을 제거하거나 이동한다. 사실, 버전 관리를 하고 있다면 불필요한(불필요해질) 내용은 다 제거해도 된다.
  3. 외부에서 새 Astro 프로젝트를 생성하여 기본 구조를 확인한다.
  4. src 디렉터리와 설정 파일을 기존 프로젝트로 복사해 온다. package.json, tsconfig.json 등 겹치는 설정 파일은 그대로 덮어 쓰거나, 비교해서 필요한 기존 설정을 살린다.
    • 패키지 매니저로 pnpm을 쓴다면, 일부 패키지를(@astrojs/check, sharp) 의존성에 직접 추가해야 한다. 빌드를 시도할 때 Astro에서 무엇이 필요한지 알려준다.
  5. npm i

이제 와서 생각해 보니, 마이그레이션 작업이 끝날 때까지 기존 src를 살려 둘 필요는 없었다. 실제로는 다른 디렉터리에 블로그 코어를 또 클론해서 (기존 Gatsby 작업물이 있던) main 브랜치를 보면서 작업했다.

블로그는 그대로 GitHub Pages로 호스팅할 예정인데, 블로그 코어 레포와 연결되므로 astro.config.mjs에 base path도 함께 설정했다.

2. 게시글 끌어오기: 콘텐츠 컬렉션 세팅

Astro에서는 콘텐츠 컬렉션을 통해 콘텐츠의 스키마를 정의하고, 콘텐츠 데이터를 가져온다. 어떤 파일을 가져올지, 스키마가 어떠한지는 src/content.config.ts 파일에 작성한다.7

주의할 점은 Astro에서 제공하는 Zod는 아직 구 버전이라는 점이다. 2025년 7월에 Zod 4 버전이 나왔지만, 2025년 11월 현재 Astro 5.16에서는 Zod 3.25 버전을 사용한다.8 API 열람 시 최신 버전이 아니라 Zod 3 버전을 확인해야 한다.

게시글은 별도 레포에 MDX 포맷으로 기록하고 있다. blog-post 레포를 클론해 오는 스크립트를 작성해서 실행했다. 게시글 스키마를 정의했고, 나중에는 카테고리 정보 스키마도 정의하여 가져오도록 했다.

3. 컴포넌트 변환 (React/Gatsby → Astro)

Gatsby에서 사용하던 React 컴포넌트를 Astro 컴포넌트로 변환하는 절차도 마이그레이션 문서에 잘 정리되어 있다.5 동적 라우트(예: blog/category/[...categoryYear])에서 여러 페이지를 생성하는 방법은 콘텐츠 컬렉션 문서에 나온다.7

  1. React 컴포넌트의 return 구문을 HTML 템플릿 영역으로 가져온다.
  2. <Link>, {children}, className 등 Gatsby/React 표현을 HTML 표준이나 Astro에 맞게 변경한다.
  3. 필요한 연산을 코드 펜스로 이동한다.
  4. 하위 컴포넌트도 Astro 컴포넌트로 변환하거나, 일단 React 컴포넌트로 두고 나중에 작업한다.
  5. GraphQL 쿼리를 콘텐츠 컬렉션 쿼리 및 전처리로 변경한다.

SCSS를 걷어내느라 스타일 시트도 CSS로 옮겨야 했기 때문에 작업량이 생각보다 많아졌다. SCSS 변수를 CSS 변수로 옮기고, 믹스인을 풀어헤치고, 중첩을 일일이 제거하는 작업을 진행했다. 전처리 결과를 주워다 쓸 수도 있었겠지만, 나중을 위해서 그러지 않고 하나하나 직접 보면서 판단했다. 예전에는 CSS에서는 안 되고 SCSS에서나 가능했던 것 일부가 CSS에서도 가능해졌다는 점이 꽤 흥미로웠다.

4. GitHub Actions Workflow 수정

이전에 블로그 코어나 게시글 레포의 main 브랜치에 푸시를 하면 자동으로 빌드해서 배포하도록 Gatsby에 맞는 워크플로를 작성해 놓았다. 프레임워크를 바꿨으니 워크플로 내용도 그에 맞게 바꿔야 하겠다. Astro 프로젝트를 GitHub Pages로 배포하는 공식 예제9를 기존 워크플로에 반영했다.

하지만 그게 한번에 실행되지는 않았다. 워크플로에 문제가 있어서는 아니었다. 보안 정책으로서 기본적으로 막혀 있다. 확인해 보니 withastro/action이 GitHub Marketplace에서 인증을 받은 액션이 아니라고 한다.

The action withastro/action@v5 is not allowed in [username]/[repository] because all actions must be from a repository owned by [username], created by GitHub, or verified in the GitHub Marketplace.

조금 고민하다가 Gemini를 통해 해결 방안 2가지를 확인했다.

  1. GitHub 레포의 보안 설정 변경 (일반적인 해결책)
  2. withastro/action@v5와 동일한 작업을 수행하는 단계를 내 워크플로에 풀어서 넣기

첫 번째 방법을 선택했다. 오류를 맞이했을 때부터 두 번째 방법을 생각하고 있었지만, 첫 번째 방법이 더 간단하고 빌드 과정 최신화도 쉽기 때문이다. 구체적인 과정은 아래와 같다.

GitHub 레포의 Actions 설정 화면

  1. 레포 설정 → Actions → General
  2. ‘Action permissions’에서 ‘Allow [username], and select non-[username], actions and reusable workflows’를 선택한다.
  3. ‘Allow or block specified actions and reusable workflows’ 입력란에 withastro/action@*을 작성한다. 패키지 매니저로 pnpm을 사용한다면 pnpm/action-setup@*도 추가해야 한다.
  4. ‘Save’ 버튼을 눌러서 설정을 저장한다.
  5. 실패한 워크플로를 다시 실행한다.

Astro 사용 후기

괜찮다고 느낀 점

공식 문서에 기본 개념, 시작 가이드, 마이그레이션, 라우팅, UI 구축, 콘텐츠 로드, 다양한 서비스로의 배포 가이드 등이 잘 나와 있다. 번역도 잘 되어 있다. 필요한 내용을 어렵지 않게 찾을 수 있었다.

전반적으로는 UI 프레임워크와 클라이언트 JavaScript를 제거하려 할 때 복잡해질 만한 요소를 컴포넌트, SSG 등 다양한 방법으로 해소하는 것 같았다. 웹 프론트엔드의 기초로 돌아가면서도 챙길 건 챙겼다고 할 수 있겠다. 빌드 속도도 괜찮았다. (클라이언트 스크립트라고 명시하지 않으면) 빌드 후 기본적으로 JavaScript가 안 남기 때문에, React를 활용한 SSG 결과물에 비해 페이지가 가볍다. 컴포넌트별로 스타일시트가 격리되어서 스타일시트 작성 시 고민점이 줄었다. 콘텐츠 중심 프레임워크라 그런지, 불러올 콘텐츠의 스키마를 미리 정의해 둘 수 있고, Markdown 관련 기능을 상당수 지원한다.

헷갈렸던 점

Astro.params의 필드의 타입

컴포넌트 스크립트에서 export할 수 있는 getStaticPaths() 함수에서는 params의 각 필드를 아무 타입으로든 리턴할 수 있다. 하지만 그걸 끌어오는 Astro.params의 각 필드는 string이다. 생성한 페이지에 대응되는 URL에서 그 값이 나오기 때문이다.10

---
import { getCollection } from "astro:content";

export async function getStaticPaths() {
  return (await getCollection("blogPost")).map((post) => {
    // [number, number, number]
    const [year, month, date] = post.data.date
      .trim()
      .split("T")[0]
      .split("-")
      .map((v) => parseInt(v, 10));

    return {
      params: { year, month, date, slug: post.data.slug },
      //        ^^^^^^^^^^^^^^^^^ number로 전달했지만
      props: { post },
    };
  });
}

// string으로 들어온다.
const { year, month, date } = Astro.props;
console.log(typeof year); // string
console.log(typeof month); // string
console.log(typeof date); // string
---

<!-- ... -->

하지만 이건 아래 2가지에 비하면 별것 아니다. 아래 2가지는 예시를 넣고 싶은데도 넣을 수가 없었다. MDX 코드 블록에 들어가는 Astro 코드의 컴포넌트 템플릿 부분에서 prettier-ignore 주석이 잘 안 들었기 때문이다.

compressHTML 옵션의 동작

이 옵션은 minify처럼 모든 페이지의 출력을 1줄로 만드는 기능이 아니다.11 공백 문자 처리 방식이 JSX보다는 HTML에 가깝다.12 내가 느끼기에 Astro는 공백 문자를 굉장히 보수적으로 남긴다. 여는·닫는 태그 앞뒤로 공백의 유무를 엄격하게 지킨다. 텍스트 노드가 너무 길어서 줄을 나눴을 때, 줄과 줄 사이의 줄 바꿈과 들여쓰기를 그대로 보존할 정도다.

컴포넌트 템플릿에서 Prettier와의 부조화

Prettier로 포매팅을 하면 긴 텍스트 노드가 column width 밖으로 나가지 않도록 개행과 들여쓰기가 추가된다. 그런데 빌드를 하면 이 개행+들여쓰기가 결과물에 그대로 남는다. 내 상황에서 브라우저에서는 똑같이 보여서, 다행히 나에게는 큰 문제는 아니었다. CSS white-space 속성에 따른 Prettier의 처리 방식에 따라 문제가 될 수도 있겠다.

그러나 Astro 컴포넌트 템플릿에서 텍스트 노드의 개행을 끌 수 있는 Prettier 옵션이 없다. 장문을 여러 줄로 쪼갤지 말지를 결정한다는 ‘Prose Wrap’ 옵션은 Markdown 전용 옵션이다.1314 이 설정을 바꾸더라도 Astro 파일에서는 변화가 없다. 텍스트 노드의 개행은 아래처럼 회피할 수 있다.

<p>
  {
    "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
  }
</p>

또, ‘HTML Whitespace Sensitivity’ 옵션은 기본값이 "css"이다. CSS display 속성에 따라 태그 앞뒤의 공백 문자를 다룬다. "strict"로 설정하면 태그 앞뒤의 공백 문자의 유무를 항상 반영한다.15 따라서 Astro 컴포넌트 파일에서는 이 옵션을 "strict"로 설정하는 게 좋겠다.

각주

  1. “ES Modules (ESM) and Gatsby”, Gatsby Documentation. https://www.gatsbyjs.com/docs/how-to/custom-configuration/es-modules/#current-limitations (2025년 11월 25일에 확인). (GitHub에서 확인 당시 버전 열람 가능)

  2. “remark-footnotes”, Titus Wormer. https://github.com/remarkjs/remark-footnotes/tree/5.0.0 (2025년 11월 26일에 확인).

  3. “Random "SIGSEGV" Errors in "yarn build" Following Failed/Canceled Build”, Gatsby 기여자. https://github.com/gatsbyjs/gatsby/issues/38483 (2025년 11월 25일에 확인).

  4. “Picking a Mode”, React Router. https://reactrouter.com/start/modes (2025년 11월 25일에 확인). (GitHub에서 확인 당시 버전 열람 가능)

  5. “Gatsby에서 마이그레이션”, Astro Docs. https://docs.astro.build/ko/guides/migrate-to-astro/from-gatsby/ (2025년 11월 25일에 확인). (GitHub에서 확인 당시 버전 열람 가능) 2

  6. “프로젝트 구조”, Astro Docs. https://docs.astro.build/ko/basics/project-structure/ (2025년 11월 25일에 확인). (GitHub에서 확인 당시 버전 열람 가능)

  7. “콘텐츠 컬렉션”, Astro Docs. https://docs.astro.build/ko/guides/content-collections/ (2025년 11월 25일에 확인). (GitHub에서 확인 당시 버전 열람 가능) 2

  8. “astro/packages/astro/package.json at astro@5.16.0”, Astro 기여자. https://github.com/withastro/astro/blob/astro%405.16.0/packages/astro/package.json#L173 (2025년 11월 25일에 확인).

  9. “Astro 사이트를 Github Pages에 배포하세요”, Astro Docs. https://docs.astro.build/ko/guides/deploy/github/ (2025년 11월 25일에 확인). (GitHub에서 확인 당시 버전 열람 가능)

  10. “라우팅 참조”, Astro Docs. https://docs.astro.build/ko/reference/routing-reference/#params (2025년 11월 28일에 확인). (GitHub에서 확인 당시 버전 열람 가능)

  11. “compressHTML in config don't work as expected · Issue #8496 · withastro/astro”, Astro 기여자. https://github.com/withastro/astro/issues/8496 (2025년 11월 28일에 확인).

  12. “new line causing spaces to appear inconsistently · Issue #12554 · withastro/astro”, Astro 기여자. https://github.com/withastro/astro/issues/12554 (2025년 11월 28일에 확인).

  13. “Option Philosophy”, Prettier Docs. https://prettier.io/docs/option-philosophy (GitHub에서 확인 당시 버전 열람 가능)

  14. “Options”, Prettier Docs. https://prettier.io/docs/options#prose-wrap (2025년 11월 28일에 확인). (GitHub에서 확인 당시 버전 열람 가능)

  15. “Options”, Prettier Docs. https://prettier.io/docs/options#html-whitespace-sensitivity (GitHub에서 확인 당시 버전 열람 가능)