Gatsby로 개인 블로그 개발하기
v1 개정 2025.02.09
2025.01.17
· Updated 2025.02.09
개인 프로젝트 모음집

🔍
노션으로 쓴 포스트가 내 블로그에 자동으로 올라갈 수 있을까?
개발자가 되기로 결심하고 티스토리 블로그를 운영한 지 2년이 지났어요. 포스트를 작성하기에 불편함이 거의 없고, SEO최적화 측면에서 거의 손대지 않아도 되는 등 많은 이점이 있었습니다. 또, 멋진 개발자 분들을 구독하면 그분들의 새로운 포스트도 볼 수 있었죠.
그러나 삼성 청년 SW 아카데미를 졸업하고 취업 준비를 하면서 한 가지 생각이 들었습니다.
“개발자라면 나만의 기술 블로그를 가져야 하지 않을까?”
이 질문을 계기로 나는 어떤 개발자가 되고 싶은지에 대해 고민하기 시작했어요. 단순히 코드만 잘 짜는 개발자가 아니라, 지식과 경험을 기록하고, 생각을 공유하며, 더 나아가 끊임없이 성장할 수 있는 개발자가 되고 싶었습니다.
그 고민의 결과로 탄생한 것이 이 기술 블로그입니다.
이번 글에서는 기술 블로그를 개발한 과정을 소개하고, 이를 통해 얻은 인사이트와 기술적 선택 과정을 공유하려고 해요. 이 글이 기술 블로그를 시작하거나 개선하고자 하는 여러분에게 작은 아이디어를 제공할 수 있기를 바랍니다.
2024년 12월 1일, 블로그를 만들기로 결정하고 노션 페이지를 열어서 구현해야 할 목표에 대해서 적기 시작했습니다. 당시에는 티스토리와 같은 기존 블로그 서비스에 비해 분명한 장점을 가진 블로그를 만들어야만 의미가 있을 것이라고 생각했습니다. 그래서 티스토리 블로그나 다른 블로그를 사용하면서 불편했던 점들과 그 해결 방법에 대해서 먼저 작성했습니다. 결론적으로, 데이터 소스는 Notion, 블로그 개발은 Gatsby, 그리고 세 가지 목표를 선정했습니다. 각각의 기술을 도입한 이유 또는 해결책을 아래에 정리했어요.
Notion(이하 ‘노션’)을 포스트 작성 Editor로 사용하려는 생각은 원래 가지고 있었어요. 개발을 하면서 가장 많이 사용하는 문서 작성 도구이며 노션만큼 직관적인 인터페이스가 구현된 서비스는 없었기 때문이에요. 즉, 친숙함편리함을 둘 다 잡을 수 있는 노션이 제 데이터 소스이자 CMS를 담당해주기로 했습니다.

Fig. 1. Notion API

그렇다면 노션에 작성한 결과물을 내 블로그에 어떻게 가져올 지를 고민할 수 밖에 없겠죠. 하지만 노션은 확실한 해결책인 Notion API를 제공합니다. 이를 사용하면 노션 페이지, 데이터베이스에 접근해 데이터를 가져올 수 있습니다.

Fig. 2. 노션 데이터베이스 - 블로그 카테고리의 일대일 대응

Notion API로 위의 그림과 같이 각 데이터베이스가 하나의 카테고리로, 각 페이지가 하나의 포스트로 일대일 대응될 수 있을 것으로 판단했습니다.
Gatsby(이하 ‘개츠비’)를 프레임워크로 선정한 이유는 꽤 많은 기술 블로그에서 사용된 안정적인 프레임워크라는 점입니다. 2025년 1월 18일 기준 LINE Engineering, 올리브영, 뱅크샐러드, 데브시스터즈 등이 이 개츠비로 기술 블로그를 서비스하고 있습니다. 또, 커뮤니티가 다른 프레임워크에 비해 커서, 플러그인 개발이 활발한 것도 한 몫했습니다.

Fig. 3. 세부적인 분류를 위해 글머리에 대괄호([])로 구분해놓은 모습

티스토리는 카테고리를 최대 depth 2까지 표현할 수 있습니다. 그래서 저를 포함한 다른 개발자 블로그의 일반적인 카테고리 분류 방식은 다음과 같습니다.
  • 일반적인 분류 방식:
    • 대분류 - 중분류
    • 예시) 프로그래밍 언어 - Java, DB - DBMS, 일상 - 일기
  • 하지만, 더 세부적으로 분류하고 싶다면 [JVM]과 같은 태그를 글머리 추가해야만 합니다. 이런 방식은 보기에도 깔끔하지 않고, 체계적인 구조로 느껴지지 않았습니다.
    이를 해결하기 위해, 트리 구조의 카테고리 시스템을 블로그에 도입하고자 했습니다. 아래는 이를 표현한 목업 이미지입니다.

    Fig. 4. 트리 구조 카테고라이징 목업

    이 구조를 통해, 무제한 깊이로 카테고리를 확장할 수 있으며 다음과 같은 구성이 가능합니다.
  • 프로그래밍 언어
    • Java
      • JVM
      • Spring Framework
        • 기초
        • 리액티브 프로그래밍
    • Python
      • Django
      • FastAPI
  • 이 방식으로 블로그 컨텐츠를 체계적으로 관리할 수 있을 뿐만 아니라, 원하는 내용을 빠르게 찾을 수 있을 것으로 생각했습니다.
    포스트를 작성할 때, 시리즈 형태로 작성할 때가 자주 있습니다. 아래는 제가 기존 블로그에서 진행했던 ‘JDK 부수기’ 시리즈의 일부입니다. 이 시리즈는 JDK를 깊이 파헤쳐보는 것을 목표로 작성된 글입니다.

    Fig. 5. JDK 부수기 시리즈

    티스토리에서 글을 시리즈로 묶기 위해서는 다음과 같은 방식이 최선이지만, 형식이 다소 제한적이고 체계적이지 않다는 아쉬움이 있었습니다.
    책(Book) 기능을 통해 시리즈를 더욱 깔끔하고 체계적으로 제공하기로 했습니다. 생각한 기능을 요약해보면 다음과 같습니다.
  • 책처럼 목차를 제공해 다음 포스트로 쉽게 이동할 수 있도록 구성
  • 책 안에서는 각 포스트를 ‘Chapter’로 표현해 독자가 책을 순서대로 읽는 듯한 경험을 할 수 있도록 디자인
  • 이 기능을 통해 기술을 깊게 다루는 긴 시리즈를 가독성 높게 만들 수 있을 것이라고 생각했습니다.
    기존 블로그에 작성된 포스트 중, 작성 당시에는 작동했지만 시간이 지나며 기술 스택이 업데이트되거나 기술이 더 이상 지원되지 않아 '죽은 포스트'가 되는 경우를 종종 보았습니다. 이러한 문제의 원인은 시간이 지남에 따라 포스트가 업데이트되지 않고 그대로 남아 있기 때문이라고 생각했습니다.
    따라서 포스트를 각 단락 별로 관리해, 시간이 지나도 최신 상태를 유지하고, 변경 사항을 쉽게 알 수 있도록 해야 한다고 판단했습니다.
  • 글의 변경사항을 Notion에 기록해 독자에게 업데이트 내역을 공개
  • Git과 같이 변경된 내용을 시각적으로 비교할 수 있는 Diff View 제공
  • 최신 정보를 항상 반영해 신뢰성을 높임
  • 세 가지 목표를 선정하고 나니 이제 프로젝트의 방향이 명확해졌어요. 간단하게 기획을 마치고 다음으로는 제가 생각한 해결책을 무엇을 이용해서, 어떤 식으로 풀어갈 수 있을지 재료들의 가능성을 확인했습니다.
    Don’t reinvent the wheel(바퀴를 다시 발명하지 마라)
    노션 API를 사용해서 기초 공사를 시작하는 것은 좋은 생각이지만, 대게 이미 만들어져 있는 라이브러리를 사용하는 것이 더 나은 방법일 수 있습니다. 아래는 제가 확인한 노션 관련 라이브러리들입니다.

    Fig. 6. react-notion-x의 test suite 중 일부

    react-notion-x는 Notion API로 가져온 데이터를 Notion과 유사한 React 컴포넌트로 재구성하는 랜더러입니다. 노션에서 제공하는 대부분의 블록 타입을 지원하며 페이지 ID만 제공하면 결과물을 자동으로 만들어 주는 편리한 녀석입니다. 레포지토리에서 Potion.so를 언급하는 걸 보니 노션으로 웹사이트를 만들어주는 서비스에 해당 라이브러리가 사용된 것 같네요. 포트폴리오나 간단히 웹사이트를 만들고 싶은 분께 좋은 선택지가 될 수 있겠습니다.
    하지만 레이아웃이 너무 제한적이라는 점이 저에게는 단점이었습니다. 블로그 디자인을 자유롭게 꾸미고 싶었던 제 경우에는 맞지 않아 선택지에서 제외했습니다.
    gatsby-source로 시작하는 라이브러리들은 개츠비에서 사용할 수 있는 소스 플러그인입니다. gatsby-source-notion은 Notion API로 받은 데이터를 MDX로 변환해 마크다운 파일 내에서 JSX를 작성할 수 있게 합니다. 하지만 가장 최근 커밋 날짜가 3년 전이라 신뢰가 가지 않아 제외했습니다. 또한, 데이터를 굳이 마크다운 파일로 변환해서 저장해야 하나 싶은 생각도 들었습니다. 어쨌거나 패스.
    한국 개발자가 gatsby-source-notion-api 플러그인의 버그를 수정해 포크한 플러그인입니다. 버그의 원인은 Notion API의 리퀘스트 제한으로 인한 것으로 보입니다. Notion API는 평균적으로 초당 3개의 요청만 허용하며 너무 많은 요청이 들어올 경우 429 에러를 반환합니다. 이 개발자 분의 포스트에 영감을 받아 429 에러가 들어왔을 때는 같은 요청을 반복적으로 보내 에러를 회피하는 방식을 제 플러그인에도 도입했습니다.
    결론적으로, 제 입맛에 맞는 라이브러리나 플러그인을 찾을 수 없었습니다.

    Fig. 7. 기존 라이브러리와 내 유즈 케이스의 차이점

    왜냐하면 제가 구현하려던 트리 형태의 카테고리를 위해서는 데이터베이스 안에서 또 다른 데이터베이스를 재귀적으로 호출하고, 최종적으로 리프 데이터베이스 안에서 페이지를 찾아야 했습니다. 하지만 대부분의 플러그인들은 하나의 데이터베이스만 호출하거나 데이터베이스 배열을 집어넣어 개별적으로 호출해야 했습니다.
    그래서 제가 직접 플러그인을 만들기로 결정했습니다.
    결론 : Notion 플러그인 직접 만들자
    다음으로는 버전 관리에 대한 고민이었습니다. 각 문단을 ID로 분리하고, 이전 버전의 내용, 수정 시간 등을 체계적으로 관리해야 했습니다.
    처음 생각은 MongoDB나 Supabase 같은 클라우드 데이터베이스 서비스를 사용할 생각이었습니다. 그러나 블로그 글을 작성하거나 빌드할 때만 데이터베이스를 사용하는 상황에서는 과한 선택처럼 느껴졌습니다. 따라서 배보다 배꼽이 큰 ‘배배커’가 될 수 있다는 판단이 섰습니다.
    그래서 이전 버전의 단락들을 최신 문단 위에 나열하고, 이전 단락에 접두사를 추가해 버전 관리 블록임을 표시하는 방식을 떠올렸습니다. 예를 들면 다음과 같습니다.

    이렇게 노션에 작성하면, 개츠비에서 파싱할 때 $version 접두사를 확인하고 이를 버전 관리용 블록으로 인식하는 것이죠. 이 방식으로 구현을 완료했기 때문에 결과를 보여드릴 수 있겠네요. 결과는 다음과 같습니다.
    명왕성은 왜소행성입니다.
    다음으로는 블로그를 만들기 위해 필요한 기술 스택을 정리해야 합니다. 알고 있는 것과 배워야 할 것을 정리하는 것이 프로젝트를 진행하는 데 빠른 길이라고 생각했습니다.
  • Gatsby
    • 이유 : 메인 프레임워크
    • 지식 정도 : 하
  • React
    • 이유 : Gatsby를 사용하기 위한 기반 기술
    • 지식 정도 : 중상
  • GraphQL
    • 이유 : Gatsby를 사용하기 위한 기반 기술
    • 지식 정도 : 하
  • Netlify
    • 이유 : 블로그 호스팅
    • 지식 정도 : 하
  • 저는 백엔드 개발자이기 때문에 이 기술스택들이 다소 낯설었습니다. React는 지난 여러 프로젝트에서 프론트엔드 개발을 담당하며 익숙했지만, 블로그는 정적 페이지로서 제가 평소 프로젝트에서 사용했던 기술과는 완전히 달랐습니다. 굳이 Redux(개츠비는 내부적으로 사용함)나 Zustand를 사용해 상태 관리를 하거나, Axios나 Fetch API로 데이터 페칭을 할 필요가 없었죠. 대신 GraphQL을 새롭게 배워야 했습니다.
    이 단계까지 오는데 5일이 걸렸습니다. 이후 약 2주 동안 Gatsby와 GraphQL, Netlify를 학습한 후 문제가 없다고 판단해 블로그 개발에 돌입했습니다.
    개츠비는 정적 웹 페이지를 만들기 위한 소스를 파일시스템이나 타사 CMS에서 가져옵니다. 그리고 빌드할 때 소스를 정제해서 GraphQL 노드로 추가하고 쿼리해 사용하는 패러다임을 추구합니다. 즉 개츠비는 어느 곳에서나 컨텐츠를 가져올 수 있습니다. 좀 더 이해하기 쉽게 개츠비 튜토리얼에서 제공하는 그림을 가져왔습니다.

    Fig. 8. source plugins을 통해 GraphQL Data Layer로 가져와서 쿼리를 통해 사용

    그림을 설명하자면 다음과 같습니다. 프로젝트 내 파일들(Filesystem), CMS, Private API, Database등 데이터를 제공할 수 있는 대부분의 공간에서 데이터를 가져옵니다. 데이터를 가져와서 GraphQL Data Layer에 추가하기 위해서는 source plugins의 도움이 있어야 합니다. 소스 플러그인에서 데이터를 가져와서 GraphQL 노드 형식에 맞게 정제해서 추가하는 것이죠. 앞선 기획 단계에서 트리 구조 카테고리를 구현하기로 했기 때문에 저는 gatsby-source-notion-churnotion이라는 소스 플러그인을 직접 만들기로 했습니다.

    Fig. 9. source plugin으로 gatsby-source-notion-churnotion 개발

    개츠비에서 정적 웹 페이지 빌드는 크게 Bootstrap phase(이하 ‘부트스트랩’)Build phase 두 단계로 진행됩니다. 그리고 부트스트랩 단계 속에는 정적 웹 페이지를 만들기 위한 세부적인 단계들이 존재합니다. 그래서 소스 플러그인을 비롯한 개츠비 플러그인을 만들 때는 이 부트스트랩 단계 중 일부를 오버라이드하거나 확장해 새로운 기능을 추가합니다.
    gatsby-source-notion-churnotion에서는 플러그인 로드 여부를 확인하기 위한 onPluginInit, 소스 노드를 알리고 플러그인의 메인 로직 역할을 하는 sourceNodes, 노션 데이터의 스키마를 GraphQL에 정의하기 위한 createSchemaCustomization, 마지막으로 모든 확장 플러그인이 호출된 후 부트스트랩 단계가 끝날 때 호출되는 onPostBootstrap를 오버라이드해서 사용했습니다.

    Fig. 10. 빌드 과정 중 gatsby-source-notion-churnotion 플러그인 호출 과정

    여기서는 모든 내용을 자세하게 적기 보다 sourceNodes 단계에서 데이터베이스를 재귀적으로 호출하는 getPages() 메서드를 구현한 과정에 대해서 설명하겠습니다.
    getPages() 메서드는 위에 잠깐 언급한 것처럼 sourceNodes라는 개츠비 부트스트랩 단계에서 호출돼, 루트 데이터베이스부터 재귀적으로 데이터베이스와 페이지들을 호출하고 정제해 GraphQL 노드에 추가시키는 플러그인의 메인 메서드입니다.
    Notion API의 API 레퍼런스의 엔드포인트 항목을 보면 각 데이터베이스와 페이지에 대해서 CRUD가 가능합니다. 저는 데이터베이스 안의 컨텐츠를 읽어야 하기 때문에 다음과 같이 데이터를 읽어오는 API를 사용했습니다.
    curl -X POST 'https://api.notion.com/v1/databases/897e5a76ae524b489fdfe71f5945d1af/query' \
      -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
      -H 'Notion-Version: 2022-06-28' \
      -H "Content-Type: application/json" \
    Shell
    Notion API로 데이터베이스를 쿼리하려면 해당 데이터베이스에 권한이 부여되어야 합니다.
    가장 상위의 루트 데이터베이스를 쿼리하면 다음과 같은 응답을 받을 수 있습니다.
    {
        "object": "list",
        "results": [
            {
                "object": "page",
                "id": "1234abcd-1234-1234-1234-123412341234",
                "created_time": "2025-01-26T06:48:00.000Z",
                "last_edited_time": "2025-01-26T06:48:00.000Z",
                "created_by": some object,
                "last_edited_by": some object,
                "cover": null,
                "icon": null,
                "parent": some object,
                "archived": false,
                "in_trash": false,
                "properties": {
                    columns
                },
                "url": "url",
                "public_url": null
            },
            ...
        ],
        "next_cursor": null,
        "has_more": false,
        "type": "page_or_database",
        "page_or_database": {},
        "developer_survey": "url",
        "request_id": "uuid"
    }
    Json
    results 안의 각각의 객체들이 하나의 페이지입니다. 그럼 다시 각각의 페이지의 id를 기준으로 쿼리하면 페이지 내부 블록들의 리스트를 응답받을 수 있습니다. API는 다음과 같습니다.
    curl 'https://api.notion.com/v1/blocks/1234abcd-1234-1234-1234-123412341234/children?page_size=100' \
      -H 'Notion-Version: 2022-06-28' \
      -H 'Authorization: Bearer '"$NOTION_API_KEY"''
    Shell
    위와 같이 쿼리하면 페이지 내 블럭에 대한 정보를 JSON형태로 받을 수 있습니다.
    이제 이를 재귀적으로 쿼리를 하면 루트 데이터베이스부터 리프의 페이지들까지 전부 가져올 수 있는데요. 한 가지 문제가 있었습니다. 노션 데이터베이스의 레코드에는 또 다른 데이터베이스를 바로 넣을 수 없었습니다. 이는 한 데이터베이스 하위에 또 다른 데이터베이스를 트리 형태로 구성하는 데 꼭 필요한 요소였습니다. 데이터베이스 아래에 또 다른 데이터베이스, 그 아래에 또 다른 데이터베이스, 그리고 마지막에 포스트가 될 페이지들..이렇게 구성해야 했거든요.

    노션 데이터베이스에 또 다른 데이터베이스를 넣을 수 있을까?

    그래서 데이터베이스의 이름을 가진 페이지 안에 또 다른 데이터베이스를 블럭으로 집어넣는 것으로 해결했습니다. 다음과 같습니다.

    페이지 내용에 동명의 데이터베이스가 있으면 카테고리로 판단

    이런 식으로 구성하면, 페이지의 블럭을 쿼리했을 때 가장 상위에 있는 블럭의 id가 곧 데이터베이스의 id가 되므로 다시 그 데이터베이스를 쿼리하면 재귀적으로 데이터베이스들을 호출할 수 있습니다.

    페이지를 카테고리와 포스트로 구분짓는 로직

    피그마로 간단하게 표현한 와이어 프레임

    와이어 프레임은 머리로 구상해 놓은 레이아웃을 표현한다고 생각하고 메인 페이지, 포스트 페이지, 소개 페이지만 피그마로 간단하게 만들었습니다. 그리고 메인 컬러 정도만 정해뒀습니다.

    관련 포스트

    © Churnobyl 성철민
    Contact: tjdcjfals@gmail.com