블로그를 바꿔보자 Gatsby -> Astro
2025-04-02

최근 블로그를 Gatsby -> Astro로 바꿨다, Astro만의 고유한 아키텍처 때문에 정적컨텐츠에 강력한 성능을 보인다고 하기 때문이다. 블로그 소스자체가 작아서 Astro의 장점과 기능을 모두 알아보지는 않았지만, 그래도 소소한 결과물을 얻었다. 기존 Gatsby를 사용할때와 비교하면 많은 plugin 및 의존성을 제거해 전체적으로 가벼워진 결과를 얻었기 때문에 그 기록을 작성하려 한다. 🤔


왜 Astro로 바꿀생각을??

state of fe 2024를 보던중에 gatsby가 숨만 붙어있다는 문장을 보게되었다. 😢

돌이켜보면 블로그를 gatsby로 개발하면서도 썩 간단하고 사용하기 편하다라는 느낌은 받지 못했다. 일단 graphQL을 사용해야하는데 이게 생각보다 불편했고 (물론 gatsby가 아니라 graphQL의 문제일수도 있다) 여러 플러그인을 통해 확장성을 가져가는데 간단한 기능도 별도의 플러그인을 설치해야해서 의존성이 너무 많아지는 문제가 생겼다. 추가로 블로그 포스트수에 비해 빌드타임도 너무 오래걸리는 느낌을 받았다..

image
처참한 Gatsby..

마침 Astro에 대해서 궁금함도 있어서 공식문서를 봤고 독특한 아키텍처로 Blog같은 정적컨텐츠 페이지의 경우 거진 Zero-Js가 가능해 빌드타임이 많이 줄고, 설정역시 많이 가벼워질것 같아 지금이 마이그레이션할 순간이다! 라고 생각해 진행하게 되었다. 🥲


Gatsby -> Astro로 마이그레이션!

의존성 정리 및 설정파일 바꾸기

Astro는 처음이니 CLI를 통해 프로젝트 템플릿을 만들어 옮기면서 마이그레이션 하려 했지만, 사이즈가 크질 않으니 그냥 진행했다, 그런고로 제일 처음에 Astro 설치 후 의존성 정리 및 설정파일 변경부터 해줬고, Astro는 react나 svelte등 다른 프레임워크와도 호환이 가능하지만, 의존성정리가 목적인만큼 최대한 Astro만 사용하기위해 react관련 의존성도 전부 지워줬다.

// (기존) gatsby-config.ts
const path = require("path");

module.exports = {
siteMetadata: {
  title: "title"",
  siteUrl: "site-url,
},

plugins: [
  "gatsby-plugin-image",
  "gatsby-plugin-sharp",
  "gatsby-transformer-sharp",
  {
    resolve: "gatsby-source-filesystem",
    options: {
      name: "blog",
      path: `${__dirname}/blog`,
    },
  },
  {
    resolve: "gatsby-plugin-mdx",
    options: {
      gatsbyRemarkPlugins: [
        {
          resolve: "gatsby-remark-images",
          options: {
            maxWidth: 1024,
            withWebp: true,
          },
        },
      ],
    },
  },
  {
    resolve: "gatsby-plugin-root-import",
    options: {
      resolveModules: [path.join(__dirname, "src")],
    },
  },
  {
    resolve: "gatsby-plugin-manifest",
    options: {
      name: `${NAME}'s Blog`,
      short_name: NAME,
      start_url: "/",
      display: "standalone",
      icon: "favicon.png",
    },
  },
  "gatsby-plugin-postcss",
],
graphqlTypegen: {
  typesOutputPath: "./src/types/gatsby-types.d.ts",
},
};

// (변경)astro.config.ts
import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx";

import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
site: "https://www.c2kim.com",
integrations: [mdx()],
vite: {
  plugins: [tailwindcss()],
},
});

너무 시원해졌다.😅 물론 gatsby로 만들 당시에도 모든 설정을 아는게 아니었기때문에 불필요한 플러그인이 있을수도 있지만, 감안해도 결과는 너무시원해졌다.

Collection 생성 및 tsconfig.json 변경

Astro의 mdx플러그인으로 파일자체가 페이지가 될 수 있지만 각 포스트마다 디렉토리로 관리하고있고 컨텐츠 관리에 컨텐츠 컬렉션을 추천해서 바꿔줬다.


컨텐츠 컬렉션은 DB의 Table과 비슷한 개념이다. src/content/ 내부 디렉토리를 컬렉션으로 인식하며 별도의 설정 content.config.ts을 가진다.

// src/content.config.ts
import { glob } from "astro/loaders";
import { defineCollection, z } from "astro:content";

const blog = defineCollection({
loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }),
schema: z.object({
  title: z.string(),
  date: z.string().date(),
  slug: z.string(),
  description: z.string(),
  references: z.optional(z.string().array()),
}),
});

export const collections = { blog };

loader를 통해 데이터를 로드하고, zod가 내장되어있어 스키마 검증도 가능하며, 위에는 없지만 reference 메서드를 통해 다른 컬렉션과의 참조도 가능하다. 현재 typescript를 사용하고 있으니 astro sync cli를 통해 컬렉션에 대한 타입정의를 생성해 줄 수 있다.

하지만! 여기서 첫번째 문제가 생겼다.. 위의 astro:content를 import하는 과정에서 typescript가 모듈을 잡지를 못했다. 오랜 삽질끝에 알아보니 astro sync를 통해 타입을 생성하면 .astro 디렉토리가 생기는데. 내 tsconfig.json에 그 .astro 디렉토리 내부의 타입을 잡는게 빠져있어서 그랬다.

원래 cli를 통해 astro project를 생성하면 해당 세팅이 자동으로 들어가서 신경안써도 되는 부분이었는데 내경우에는 아니었기때문에 그랬다.

// tsconfig.json
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", ...], // this
"exclude": ["dist"],
}

참고로 컬렉션의 에디터에서 스키마 검증, 자동완성 등 타입검사를 위해 astro에서 제공하는 설정을 확장해줘야 했고 이후 include에 컬렉션 타입 정의파일을 추가 해주니 정상적으로 해결됐다.

Astro 컴포넌트로 변경 + InlineTrim 컴포넌트 생성

Astro 컴포넌트는 기본적으로 RSC처럼 서버에서 렌더링 된다. 그래서 별도의 javascript를 추가하려면 front-matter에 넣어줘야 한다. 클라이언트와 상호작용이 필요한 소스같은 경우에는 <script> 태그나 별도 컴포넌트로 분리해 client: 지시어를 넣어줘야 한다.


하지만 블로그같은경우에 인터랙션할일이 없기 때문에 기존 gatsby에서 쓰던 컴포넌트 소스를 긁어오는걸로 충분했다.

// layout.astro
---
import Nav from "@components/nav.astro";
---
<html lang="ko-KR">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link rel="icon" type="image/png" href="/favicon.png" />
  <link rel="stylesheet" href="/public/styles/syntax.css" />
  <link rel="stylesheet" href="/public/styles/global.css" />
</head>
<body>
  <main class="w-screen h-screen flex flex-col items-center">
    <div
      class="w-full h-full flex flex-col flex-1 items-center max-w-[1440px]"
    >
      <Nav />
      <slot />
    </div>
  </main>
</body>
</html>

// [slug].astro
---
export async function getStaticPaths() {
  const posts = await getCollection("blog");
  
  return posts.map((post) => ({
      params: { slug: post.id },
      props: post,
  }));
}

const post = Astro.props;
const { Content } = await render(post);
---

<Layout>
<Post post={post}>
  <Content
    components={{...}}
  />
</Post>
<Comments />
</Layout>

slot 컴포넌트는 children을 의미하고. JSX와 유사한 표현식을 사용하기때문에 별다른 이질감은 없었다.


기본적으로 SSG기 때문에 getStaticPaths()함수는 필수며 모든 포스트를 받아오고 받아온 포스트를 render 메서드를 이용해 Content 컴포넌트를 생성하고 렌더링해주면 포스트가 렌더링된다. Content 컴포넌트의 components props 내부에 mdx파일 내부에서 사용할 custom-component들을 넣어주면 된다. 무엇보다 graphQL을 걷어내니 직관적이게 되서 좋았다. 🎊

하지만 mdx를 파싱하는 과정에서 컨텐츠 내부에 gatsby에는 없던 앞뒤 공백이 생겼다. 다른 블록-엘리먼트는 상관없는데 span, a등 인라인 엘리먼트에서 공백이 생기면서 스타일이 틀어져 문제가 됐다. Astro 공식문서를 참조해 inline-trim이란 컴포넌트를 사용해 해결해줬다.

---
import type { HTMLAttributes, HTMLTag } from "astro/types";

type Props<T extends HTMLTag> = HTMLAttributes<T> & {
tag: T;
};

const { tag: Tag, ...props } = Astro.props;
const content = (await Astro.slots.render("default")).trim();
---

<Tag set:html={content} {...props} />

이런 다형성 컴포넌트 생성이라던지 기능이 많아서 블로그뿐 아니라 다른 사이드프로젝트에서도 한번 사용해보고싶단 생각을 살~짝 했다.


근데 prettier가 대체 왜 안먹냐..

vscode에 공식 Astro 익스텐션이 존재하고, 공식문서에는 설치만 하면 prettier 설정까지 자동이라했는데 안됐다.. 사실 그냥 무시할까도 싶었는데 마이그레이션하면서 save시 자동으로 포매팅이 안되다보니까 마이그레이션 속도에도 영향이가 승질이나서 수정했다..

github 이슈에도 보니 꽤 나같은사람이 많았는데 설정을 하나씩 삽질하면서 바꿔보니 astro의 formatter도 다른 언어와 마찬가지로 esbenp.prettier-vscode 였던게 문제였다. extension이 제공하는 formatter로 변경해주니 잘 먹었다.

// settings.json
"[astro]": {
  "editor.defaultFormatter": "astro-build.astro-vscode"
}

한김에 tailwind 까지 버전업 하자 v3 -> v4

큰건 아닌데 마이그레이션 하면서 버전업을 해줬고 v3 -> v4로 오면서 자유도를 위해 tailwind에서 기본으로 먹는 style을 많이 제거해서 그런지. 기존 블로그 스타일과 많이 바뀌어서 틀어진 부분을 맞춰줬다. 하면서 가독성이 살짝 의심된 나의 핑크핑크한 code-syntax 테마를 바꿔줬다.

astro에서 Shiki를 이용해 내장된 syntax 지원이 있어 기존 hljs에서 바꾸고 테마를 변경해줬다. 🥹 (핑크테마가 그립긴하다)


결과물과 느낀점은?

package.json이 매우 가벼워졌다.

기존의 개발의존성 까지 합치면 23개 였는데.. 7개가 되버렸다.. 상당히 만족한다. 😄

설정파일이 매우 가벼워지고 직관적이게 바뀌었다.

위에서도 말했지만 기존의 설정파일을 열어보면 이게 뭐였지 싶을정도로 너무 많았는데 직관적이게 변해서 좋았다.

놀라운 빌드시간 단축

24s -> 2s라는 놀라운 결과를 얻었다. 첫 포스트인 cra에서 vite로 마이그레이션때의 빌드타임 개선만큼의 감동을 받았다. 🥹

image
[before] - gatsby
image
[after] - astro