FastAPI로 Docusaurus와 유사한 사이트 구축하기: 4단계 - Frontmatter 파싱
Olivia Novak
Dev Intern · Leapcell

이전 글에서 Markdown 코드 블록에 대한 구문 강조 기능을 추가했습니다.
하지만 문서 페이지 제목({{ page_title }})이 main.py 라우트 함수 ("page_title": "Hello, Markdown!")에 여전히 하드코딩되어 있음을 발견했을 수 있습니다. 이는 매우 비유연적입니다. 이것은 새로운 문서를 추가할 때마다 코드를 수정해야 한다는 것을 의미할까요?
문서 사이트는 유연해야 하며, 언제든지 기사를 추가하거나 제거할 수 있어야 합니다. 기사의 제목, 작성자, 날짜와 같은 메타데이터는 콘텐츠와 마찬가지로 Markdown 파일 자체 내에 정의되어야 합니다.
이 글에서는 Frontmatter(Markdown 파일 상단에 메타데이터를 정의하는 일반적인 사양)를 소개하고 FastAPI가 이를 파싱하여 메타데이터를 동적으로 로드할 수 있도록 하겠습니다.
1단계: Frontmatter 파싱 라이브러리 설치
파일에서 Frontmatter와 Markdown 콘텐츠를 분리하고 파싱하기 위해 python-frontmatter를 사용할 것입니다.
다음 명령으로 설치하세요:
pip install python-frontmatter
2단계: Markdown 문서에 Frontmatter 추가
다음으로, docs/hello.md 파일을 수정하여 맨 위에 Frontmatter를 추가해 보겠습니다.
Frontmatter 블록은 삼중 하이픈(---)으로 둘러싸입니다.
docs/hello.md 업데이트:
--- title: Hello, Frontmatter! author: FastAPI Developer date: 2025-11-09 --- ... (나머지 Markdown 콘텐츠)
여기서 title, author, date 세 가지 메타데이터 필드를 정의했습니다. 필요에 따라 더 많은 필드를 추가할 수 있습니다. title 필드는 궁극적으로 기사의 page_title로 사용됩니다.
3단계: Frontmatter 파싱을 위해 main.py 수정
이제 get_hello_doc 라우트 함수를 수정하여 간단한 open().read() 대신 frontmatter 라이브러리를 사용하여 파일을 로드하도록 하겠습니다.
frontmatter.load() 함수는 파일을 두 부분으로 파싱합니다.
post.metadata: 모든 Frontmatter 데이터를 포함하는 사전 (예:{'title': 'Hello, Frontmatter!', ...}).post.content: Markdown 콘텐츠의 본문만 포함하는 문자열입니다.
main.py를 열고 다음과 같이 수정하세요:
# main.py from fastapi import FastAPI, Request from fastapi.templating import Jinja2Templates from fastapi.responses import HTMLResponse import markdown from fastapi.staticfiles import StaticFiles import frontmatter # 1. frontmatter 라이브러리 가져오기 app = FastAPI() app.mount("/static", StaticFiles(directory="static"), name="static") templates = Jinja2Templates(directory="templates") # --- 홈 라우트 (변경 없음) --- @app.get("/", response_class=HTMLResponse) async def root(request: Request): context = { "request": request, "page_title": "Hello, Jinja2!" # (이미지와 일관성을 위해 원래 한국어로 유지되었지만, "Hello, Jinja2!"는 영어에 해당합니다.) } return templates.TemplateResponse("index.html", context) # --- 2. 문서 라우트 수정 --- @app.get("/docs/hello", response_class=HTMLResponse) async def get_hello_doc(request: Request): """ hello.md 문서를 읽고, 파싱하고 (Frontmatter 포함), 렌더링합니다. """ md_file_path = "docs/hello.md" try: # 3. frontmatter.load를 사용하여 파일 읽기 및 파싱 post = frontmatter.load(md_file_path) except FileNotFoundError: return HTMLResponse(content="<h1>404 - Document Not Found</h1>", status_code=404) except Exception as e: # 잘못된 형식의 YAML에 대한 일반적인 오류 처리 추가 return HTMLResponse(content=f"<h1>500 - Parse Error: {e}</h1>", status_code=500) # 4. 메타데이터 및 콘텐츠 추출 metadata = post.metadata md_content = post.content # 이것은 순수한 Markdown 콘텐츠입니다. # 5. Markdown 콘텐츠만 변환 extensions = ['fenced_code', 'codehilite'] html_content = markdown.markdown(md_content, extensions=extensions) # 6. 메타데이터에서 page_title 동적으로 가져오기 # 'title' 키가 누락된 경우 충돌을 피하기 위해 .get() 사용 page_title = metadata.get('title', 'Untitled Document') context = { "request": request, "page_title": page_title, # 하드코딩된 값으로 대체 "content": html_content } return templates.TemplateResponse("doc.html", context)
4단계: 실행 및 테스트
uvicorn main:app --reload를 실행하여 서버를 시작합니다.
이제 http://127.0.0.1:8000/docs/hello를 다시 방문하세요.
브라우저 탭 제목과 페이지의 <h1> 태그가 더 이상 "Hello, Markdown!"이 아니라 hello.md 파일 Frontmatter에 정의한 "Hello, Frontmatter!"로 바뀌었음을 볼 수 있습니다.
결론 및 다음 단계
Frontmatter와 이를 파싱하는 논리를 도입함으로써, 이제 기사는 사이트와 완전히 분리되었습니다.
Markdown 외에도 웹사이트에는 이미지와 같은 정적 파일도 포함됩니다. FastAPI가 이러한 정적 파일을 올바르게 배포하여 온라인에서 액세스할 수 있도록 어떻게 할 수 있을까요?
다음 글에서는 이 문제를 해결하겠습니다. FastAPI가 올바르게 배포하고 최종 웹페이지에 표시되도록 Markdown 파일에 참조된 정적 에셋(예: 이미지)을 처리합니다.
기타
사이트를 구축한 후에는 다른 사람들이 볼 수 있도록 온라인에 배포하고 싶을 수 있습니다. 하지만 대부분의 클라우드 플랫폼은 비싸고, 이와 같은 연습 프로젝트에 높은 비용을 지불할 가치가 없습니다.
더 경제적인 배포 방법이 있을까요? Leapcell을 사용해 볼 수 있습니다. Python, Node.js, Go, Rust와 같은 여러 언어를 지원하며 매월 넉넉한 무료 티어를 제공하여 비용 없이 최대 20개의 프로젝트를 배포할 수 있습니다.

