글은 노션에서 쓰고, 읽는 사람은 정적 사이트에서 본다. 노션은 편집기이자 CMS이고, Astro는 빌드 시점에 노션 API를 한 번 호출해 HTML을 만든다. 런타임에 노션을 호출하지 않으니 빠르고, 노션이 잠깐 죽어도 사이트는 살아 있다.
구조
- 노션 데이터베이스
Posts에 글을 쓴다.Status를Published로 바꾸면 발행 대상이 된다. - Astro 콘텐츠 레이어의 커스텀 로더가 빌드 때
Published글만 조회한다. - 각 글의 블록을 마크다운으로 바꾸고, Astro의
renderMarkdown으로 HTML을 만든다. - 노션이 발급한 이미지 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뿐이다. 코드는 그대로다.