Astro 블로그에서 글끼리 자동 연결하기: 관련 글·태그·시리즈·백링크
· astro, static-site, markdown, content-collections
배경
- 블로그는 Astro 정적 사이트이며 각 글은 하나의 Markdown 파일로 구성한다.
- 글 frontmatter에는
title,date,description,tags,draft가 있다. - 기존 워드프레스·티스토리와 달리 편집기, 관련 글 추천, 이전·다음 글, 태그 모음, 검색 기능이 기본 제공되지 않는다.
- 두 가지 불편함이 있었다. 첫째, 글을 수정하기 번거롭다. 둘째, 새 글이 기존 글과 연관될 때 양쪽에 링크를 추가해야 한다.
- 블로그는 “AI로 무언가를 만들고, AI로 콘텐츠를 쓰는 재미”를 목표로 하므로, 기능을 직접 구현하고 무거운 기능은 외부에서 가져다 쓰기로 했다.
- 기존에 “블로그를 만드는 과정”을 다룬 글이 3편 있었으며, 이는 시리즈 구조가 필요했다.
결론부터
- Astro 자체는 불편한 도구가 아니다. 불편함은 “CMS 없이 파일만 쓰는 구조”와 “링크를 사람이 손으로 거는 방식”에서 온다.
- 핵심 아이디어는 기존 글 파일을 고치지 않고, 빌드 시 전체 글을 읽어 연결을 계산해 보여 주는 것이다.
- 구현한 네 가지 기능은 다음과 같다.
- 관련 글 – 태그가 겹치는 글을 자동 표시.
- 태그 페이지 –
/tags/<태그>/경로에 해당 태그 글 목록 제공. - 시리즈 – frontmatter
series로 묶고, 글 아래 목록·이전·다음 링크·시리즈 페이지/series/<slug>/제공. - 백링크 – 새 글 본문에 기존 글 링크가 있으면 기존 글 아래에 “이 글을 언급한 글”을 자동 표시.
- 외부 통합을 사용하지 않은 이유는 글 구조에 맞추기 쉽고 의존성이 늘지 않으며 구현 과정 자체가 콘텐츠가 되기 때문이다.
페이지는 여러 조각으로 조립된다
- 한 글의 페이지는 Markdown 파일 하나만으로 만들지 않는다.
레이아웃에컴포넌트(헤더, 푸터, 연결 영역)와 본문을 삽입한다. - 헤더와 푸터는 레이아웃에 한 번 정의돼 모든 페이지에 적용된다.
- 연결 영역도 레이아웃에 한 번 넣으면, 빌드 시 전체 글 목록을 탐색해 해당 글과 연결된 글을 찾아 채운다. 연결된 글이 없으면 영역이 표시되지 않는다.
- 빌드 시 계산된 값을 페이지에 전달하는 곳은
getStaticPaths()의props다. - 새 태그·시리즈가 생기면
[tag].astro,[series].astro같은 동적 라우트가 각각 페이지를 생성한다.
기능별 설계
관련 글 (태그 기반)
- 태그 겹치는 개수를 기준으로 내림차순 정렬하고, 동점이면 최신순으로 정렬한다.
- 최대 3편을 표시한다. 겹치는 글이 없으면 영역을 숨긴다.
- 한계: 글이 5편뿐일 때 모든 글이 동일한
cloudflare태그만 공유하면 연관이 약한 글까지 표시된다. - 개선안 두 가지: (1) 겹치는 태그가 2개 이상일 때만 표시(간단하지만 관련 없는 글이 늘어날 수 있음), (2) 흔한 태그에 가중치를 낮추고 드문 태그에 가중치를 높이는 방식(계산 복잡). 현재는 (1)을 적용한다.
태그 페이지
- 글 frontmatter의
tags를 읽어/tags/<태그>/링크로 변환한다. - 해당 페이지에서는 같은 태그를 가진 글을 최신순으로 모아 보여준다.
- 구현이 가장 간단하다.
시리즈
- 시리즈는 연관성을 계산하지 않는다. 글 frontmatter에
series값이 정확히 일치하면 직접 연결한다. - 구현 방식 후보:
- A. 글마다
series한 줄만 추가한다. 기존 글은 건드리지 않아도 시리즈 목록이 자동 확대된다. - B. 시리즈 정보를 별도 파일에 정의한다. 새 글을 추가할 때 파일도 수정해야 하며, 글과 어긋날 위험이 있다.
- A. 글마다
- A 방식을 기본으로 선택했다. 시리즈 이름은 영문 slug(
create-astro-blog)으로 사용하고, 화면에 표시할 한글 이름은src/data/series.json에 매핑한다. series필드는src/content.config.ts스키마에series: z.array(z.string()).default([])로 정의한다. 스키마에 없는 필드는 무시된다.- 시리즈 영역에는 전체 목록(현재 글 강조), 이전·다음 링크, 시리즈 페이지 링크가 포함된다.
백링크
- 기존 글 파일을 수정하지 않는다. 빌드 시 모든 글의 Markdown 원문(
post.body)에서[제목](/blog/<slug>/)형태의 링크를 정규식으로 검색한다. - 도메인이 포함된 절대 링크도 찾는다. 자기 자신에 대한 링크와 존재하지 않는 글에 대한 링크는 무시한다.
- 링크 대상 글 아래에 “이 글을 언급한 글” 영역을 생성한다. 언급이 없으면 영역이 표시되지 않는다.
- 원문에서 링크를 찾는 이유는 빌드 전에 전체 글 목록만으로 계산이 가능하기 때문이다.
검색 색인 관점
- 시리즈 페이지는 사이트맵에 자동 포함된다. 따라서
/series/…와/tags/…도 검색 크롤러가 접근할 수 있다. - 시리즈·태그 페이지는 글 목록만 제공하므로 검색 노출 가치가 낮을 수 있다. 필요하면
noindex메타 태그를 추가할 수 있다(미적용, 미결정).
헷갈렸던 점들
- 기능을 머지했는데 화면에 태그 링크가 안 보였다
- 원인: 여러 PR을 스택 형태로 머지했으며, 앞 PR을 머지한 뒤 뒤 PR이
main이 아닌 앞 PR 브랜치에 머지돼 코드가main에 반영되지 않았다. - 해결: 모든 변경을 포함한 마지막 브랜치를
main에 단일 PR로 머지했다. 이후부터는 독립 PR을main기준으로 만든다.
- 원인: 여러 PR을 스택 형태로 머지했으며, 앞 PR을 머지한 뒤 뒤 PR이
series를 글에 적었는데 화면에 아무것도 안 나왔다- 원인: 시리즈를 읽어 그리는 코드가 아직
main에 없었다. - 해결: 코드를 배포한 뒤
series가 화면에 표시되었다.
- 원인: 시리즈를 읽어 그리는 코드가 아직
정리
- 글 사이 연결은 파일에 직접 쓰지 않고, 빌드 시 전체 글을 읽어 계산한다.
- 레이아웃에 연결 영역을 컴포넌트로 한 번 만들고, 연결된 글이 없으면 숨긴다.
- 태그 기반은 느슨한 연결(관련 글),
series는 직접적인 연결이다.series는 글에 한 줄만 적으면 되고, 순서는 작성일이 결정한다. - 시리즈 slug는 영문으로 사용하고, 화면 이름은 작은 데이터 파일에 매핑한다.
- 백링크는 새 글 본문의 링크를 빌드가 모아 기존 글에 자동 표시한다.
- PR을 여러 개 쌓아 머지할 경우 앞 PR이
main에 반영되지 않을 수 있다. 독립 PR로 올리는 것이 안전하다.