DELUJU

Notion을 CMS로 쓰는 Astro 기술 블로그 만들기

노션 데이터베이스에 글을 쓰면 Astro 콘텐츠 레이어가 빌드 때 가져와 정적 페이지로 만든다. 지금 읽고 있는 이 블로그가 그 결과물이다.

글은 노션에서 쓰고, 읽는 사람은 정적 사이트에서 본다. 노션은 편집기이자 CMS이고, Astro는 빌드 시점에 노션 API를 한 번 호출해 HTML을 만든다. 런타임에 노션을 호출하지 않으니 빠르고, 노션이 잠깐 죽어도 사이트는 살아 있다.

구조

  1. 노션 데이터베이스 Posts에 글을 쓴다. Status를 Published로 바꾸면 발행 대상이 된다.
  2. Astro 콘텐츠 레이어의 커스텀 로더가 빌드 때 Published 글만 조회한다.
  3. 각 글의 블록을 마크다운으로 바꾸고, Astro의 renderMarkdown으로 HTML을 만든다.
  4. 노션이 발급한 이미지 URL은 1시간 뒤 만료되므로 빌드 중에 public/notion/으로 내려받아 경로를 바꾼다.

노션에서 입력받는 것

속성 타입 사이트에서의 역할
Title 제목 글 제목
Slug 텍스트 URL 경로. 비우면 제목으로 생성
Type 선택 KnowHow, Tutorial 같은 글의 성격
Category 선택 목록 필터 1차 축
Tags 다중 선택 목록 필터 2차 축
Summary 텍스트 목록에 보이는 요약
Published 날짜 정렬 기준
Status 선택 Published만 노출
Thumbnail 파일 카드 뷰 썸네일
Featured 체크박스 상단 고정

로더의 뼈대

export function notionLoader(opts: Options): Loader {
  return {
    name: 'notion-blog',
    async load({ store, parseData, renderMarkdown }) {
      const pages = await queryPublished(opts);
      for (const page of pages) {
        const slug = readSlug(page);
        if (store.get(slug)?.digest === page.last_edited_time) continue;
        const md = await blocksToMarkdown(await fetchBlocks(page.id));
        const data = await parseData({ id: slug, data: readProperties(page) });
        store.set({ id: slug, data, digest: page.last_edited_time, rendered: await renderMarkdown(md) });
      }
    },
  };
}

last_edited_time을 digest로 쓰면 바뀌지 않은 글은 블록 조회를 건너뛴다. 글이 100편이 넘어도 빌드 시간은 바뀐 글 수에만 비례한다.

계정을 옮길 때

이 블로그의 노션 페이지는 회사 계정에서 먼저 만들고, 이후 개인 계정으로 템플릿을 복제해 옮길 예정이다. 사이트 쪽에서 바뀌는 것은 환경 변수 두 개, NOTION_TOKEN과 NOTION_DATA_SOURCE_ID뿐이다. 코드는 그대로다.