ASTRO

Astro, Ghost로 만든 코멘토 개발자 블로그

윤종석 프로필 이미지

윤종석

2023.07.03

11 min read

Astro, Ghost로 만든 코멘토 개발자 블로그

안녕하세요. 코멘토 프론트엔드 개발자 윤종석입니다. 지금 보고 계시는 저희 개발팀의 블로그는 AstroGhost를 이용해 만든 블로그인데요. Ghost를 Headless CMS로 사용하고 Astro로 지금 보고 계신 뷰를 구현해서 사용하고 있습니다. 제작 과정을 통해 Astro와 Ghost를 어떻게, 왜 사용했는지 짚어보도록 하겠습니다.

Astro로 빠른 블로그 만들기

블로그 제작은 먼저 Astro로 시작했는데요. 속도가 빠른 Static Site Generator를 써보고 싶은 마음이 컸는데 이번에 새로 만들 블로그가 가장 적합한 프로젝트라고 생각했습니다. Astro는 여러 가지 후보를 보다가 한번 사용을 해봤었는데 문서를 따라갔을 때 금방 익숙해질 수 있을 만큼 쉬웠고 쭉 둘러보다보니 나중에 어떤 기술이든 붙이기 쉬워보이는 장점이 있었습니다. 자체적인 문법도 JSX 베이스라서 어렵지 않았고 원한다면 React, Vue, Svelte 등 다른 프론트엔드 라이브러리도 사용할 수 있어서 이후에 어떤 상황에서 누가 관리해도 쉽게 변경이 가능할 것으로 보였습니다. 문서도 친절한 편이라 만들면서 부딪히는 문제는 공식 문서로 거의 다 해결이 가능했습니다.

---
import Layout from "@layouts/Layout.astro";
import { ghostClient } from "@lib/ghost";
import { Post } from "src/pages/posts.astro";

// Next.js같이 빌드 시점에 어떤 경로가 빌드되어야하는지 선언할 수 있는 함수다.
export async function getStaticPaths() {
    const posts: Post[] = await ghostClient.posts.browse({
        limit: "all",
        include: "authors",
    });

    return posts.map((post) => {
        return {
            params: {
                slug: post.slug,
            },
            props: {
                post: post,
            },
        };
    });
}

const { post } = Astro.props;
---

<Layout title={post.title + " | 코멘토 개발자"}>
    <article class="col-span-8 col-start-3 py-[80px]">
        <div class="flex items-center mt-2 gap-2">
            <p class="flex items-center text-gray-400 text-sm gap-1">
                <img
                    src={post.authors[0].profile_image}
                    width="24"
                    height="24"
                />
                {post.authors[0].name}
            </p>
            <p class="text-gray-400 text-sm">
                {post.reading_time} min read
            </p>
        </div>
        <h1 class="text-3xl font-semibold mt-10">{post.title}</h1>
        <img class="mt-10" src={post.feature_image} alt={post.title} />
        <article class="prose lg:prose-lg mt-16">
            <Fragment set:html={post.html} />
        </article>
    </article>
</Layout>
현재 페이지(글 보기)의 코드. '---'로 구분된 JS 부분과 아래의 JSX로 되어있다. 프론트엔드 라이브러리(특히 React와 그 파생형)를 써봤다면 익숙할 JSX 문법으로 조금만 해도 익숙해질 수 있다.

처음에는 Astro가 마크다운을 지원하기 때문에 글쓰기까지 마크다운으로 하는 걸 목표로 했었는데요. 하지만 블로그 글을 쓸 때 Rich Text Editor가 없으면 여러 가지로 불편할 것 같았습니다. 특히나 이미지를 첨부하려면 직접 어딘가 올리고 그 주소를 마크다운에 넣어주는 식으로 해줘야 했기 때문에 그 부분이 가장 문제였는데요. 이때부터 이런 처리를 해줄 수 있는 Rich Text Editor를 보유한 CMS를 찾기 시작했습니다. 그리고 Ghost를 찾게 되었습니다.

Medium에서 Ghost로 이주하기

사실 기존에도 개발자 블로그가 있었는데요. Medium으로 만든 블로그였습니다. 다만 개발 관련 내용을 기록하기에는 마크다운 문법이나 코드 블록 등을 내부적으로 지원하지 않는 아쉬움 등이 많았습니다. 그래서 Medium보다 조금 더 개발 관련 글을 쓰기 좋은 시스템을 찾아봤습니다.

Astro의 문서에는 CMS를 연결하는 방법을 알려주는 페이지도 있었는데요. 그 중 하나가 코멘토에서 이미 운영하는 파트너스 블로그에서 사용하는 Ghost였습니다. 이미 사용 중인 CMS라면 나중에 결제를 하거나 사용할 때 어떤 식이든 편할 것 같아서 자세히 알아보기 시작했습니다.

운영 중인 블로그에서 임시 글을 써보면서 테스트해보니까 텍스트 에디터가 개발 글을 쓰는데도 문제가 없어보였고요. 기존에 해둔 Astro와 통합하는 것도 패키지 하나와 약간의 설정만 해주면 금방 해줄 수 있어서 블로그 글을 불러와서 페이지를 만드는 것도 간단했습니다. (위의 예시 코드에 Ghost 글을 불러오는 코드가 상단에 있습니다.)

결제를 새로 해야하는게 조금 걱정이었는데요. 다행히 Ghost는 직접 서버를 호스팅한다면 어떤 비용도 청구하지 않습니다. (물론 호스팅 비용은 들지만 AWS 기준 Free Tier로도 충분히 시작할 수 있는 정도의 서버입니다.) EC2 인스턴스를 t2.micro로 하나 만들어서 설정을 시작해봤는데요. 공식 문서를 참고해서 우분투로 인스턴스를 만들어서 설치해봤습니다. 처음에 Security Group을 잘못 설정해서 인스턴스 접속을 못했다거나 하는 문제가 있기는 했지만 대체적으로 문서만 따라가면 문제는 없었는데요. 그 중 몇 개만 겪은 문제를 소개하자면 다음과 같습니다.

  1. ghost-cli 설치 중 인스턴스가 멈추고 뻗음
    이 경우는 순간적으로 부하가 높아지면서 인스턴스가 버티지 못한 문제였습니다. 설치 중에만 잠깐 더 높은 급의 인스턴스로 바꿔주니 바로 문제 없이 설치했고 원래 등급으로 돌려줬습니다.
  2. ghost가 MySQL에 접근할 수 없음
    ghost install 명령어를 치면 중간에 MySQL에서 사용할 계정을 물어보는데요. 이때 root를 준다면 접근을 못하는 경우가 발생합니다. root 계정을 비밀번호 로그인을 할 수 있게 변경해도 되지만 안전하게 ghost만을 위한 계정을 하나 만들어서 ghost에 제공해주는 것이 좋습니다.
    CREATE USER 'ghost'@'localhost' IDENTIFIED BY '비밀번호';
    GRANT ALL PRIVILEGES ON GhostDB이름.* to 'ghost'@'localhost';
    
    root를 사용하고 싶다면 mysql root auth_socket 검색어를 이용해 찾아보시면 관련 내용을 찾을 수 있습니다.
  3. ghost start를 정상적으로 했는데도 접근할 수 없음
    AWS 보안 그룹의 문제가 아니라면 ghost 폴더에 있는 config.production.json을 확인해보세요. 거기서 만약 server.host127.0.0.1이라면 외부에서 접근할 수 없습니다 ghost config server.host 0.0.0.0 명령어를 사용해 어디서든 접근할 수 있게 변경해주세요.

이 정도말고는 큰 이슈 없이 설치할 수 있었습니다. 해보니까 미리 필요한 nginx, MySQL만 설치해두면 명령어 하나로 서버 설정과 DB 설정을 모두 다 해주는 굉장히 편리한 툴이었습니다. 다만, Astro로 불러와보니 하나 문제가 있었는데요. 바로 이미지가 뜨지 않는다는 것이었습니다. 이미지 주소를 확인해보니 그냥 localhost가 그대로 있었습니다. Ghost는 이미지를 에디터에 올리면 로컬에 파일로 저장하는 것이 기본이었습니다.

이미지를 바로 S3에 저장하기

처음에는 이미지를 접근할 수 있게 서버 설정을 바꿔야하나 고민하기도 했는데요. 일단 코멘토가 이미 S3를 CDN까지 붙여놨기 때문에 이미지를 업로드하면 바로 S3에 올릴 수 있게 하는 방법을 우선 찾아봤습니다. 다행히 이미 제작된 패키지가 있었습니다.

저장소로 가서 ghost-storage-adapter-s3를 설치해줍니다. 처음에 ghost를 설치한 /var/www/${sitename} 폴더에서 README.md 상단에 있는 명령어 3줄을 실행해줍니다. (설치 후 해당 파일을 adapter로 넣어주는 과정입니다.)

(* 다음 내용은 S3가 이미 있다고 가정하고 글을 진행합니다. 없다면 AWS 문서나 저장소의 README를 참고해 새로운 버킷을 만들어주세요.)

먼저 Ghost가 사용할 AWS 권한을 IAM으로 만들어줍니다. Ghost만 사용할 계정을 하나 만들어서 다음 권한을 부여해줍니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${사용할 버킷 이름}"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:PutObjectVersionAcl",
                "s3:DeleteObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::${사용할 버킷 이름}/*"
        }
    ]
}

이제 설정 파일을 바꿔줘야 하는데요. ```config.production.json```이 설정 파일입니다. 에디터로 열고 직접 바꿔주셔도 되고 ```ghost config``` 명령어를 사용해 하나씩 설정해주셔도 됩니다. 저는 JSON 문법이 틀리면 찾아내기가 어려울 것 같아서 명령어를 사용했습니다. 다음과 같은 항목을 추가해줍니다.

"storage": {
    "active": "s3",
    "s3": {
      "accessKeyId": "위에서 만든 계정의 Access Key ID",
      "secretAccessKey": "위에서 만든 계정의 Secret Access Key",
      "region": "S3 버킷의 리전",
      "bucket": "버킷 이름",
      "pathPrefix": "저장할 경로",
      "assetHost": "CDN이 있다면 CDN의 호스트 주소"
    }
}

객체 안에 있는 속성을 지정하려면 ghost config storage.active s3같이 .으로 이어주시면 됩니다.

이렇게 하고 나면 에디터에 이미지를 올리면 바로 S3에 업로드되고 CDN 주소를 가지고 이미지가 글에 추가합니다. (최상단 대표 이미지가 그렇게 업로드가 되었습니다.)

배포하고 서버 붙이기

이제 글을 쓰면 배포가 가능합니다. 이번에는 Ghost CMS는 계속 EC2로 띄워두고 Astro의 빌드만 배포하기로 했습니다. Ghost CMS 인스턴스의 IP가 계속 바뀌면 안되기 때문에 고정 IP와 도메인을 부여합니다.

Astro는 빌드 결과물을 S3에 저장하는 방식으로 배포를 하기로 했습니다. Astro의 이 문서를 참고하면서 배포했습니다. 우선 빌드한 다음에 dist 폴더를 블로그 전용으로 만든 버킷과 동기화합니다. 그리고는 CloudFront가 이 버킷을 바라보게 하고 Route 53으로 도메인명을 확보해서 CloudFront와 연결해줍니다. 문서에 보시면 폴더별 index.html로 연결하기 위한 함수 추가 등 필요한 작업이 있기 때문에 빼먹지 않고 진행해줍니다.  마지막에는 Github Actions를 이용해 배포를 자동화하는 단계가 있는데 아직 규모가 작아 진행하지는 않았습니다. 여기까지 하면 지금 보시는 이 블로그가 완성됩니다.

갓 완성된 시점의 블로그. 이 글만 나와있습니다.

커리어의 성장을 돕는 코멘토에서 언제나 함께 성장할 개발자를 기다리고 있습니다. 채용 페이지에서 코멘토가 어떤 회사인지, 어떤 사람을 찾는지 더 자세히 확인해보세요. 😊