Marp IFrame 경로 에러 해결 방법
Hugo와 Marp를 통합하여 슬라이드 문서를 자동으로 변환하고 표시하는 작업 중, 경로 처리 문제로 인해 예상치 못한 오류가 발생했습니다. 이 글에서는 문제의 현상, 원인, 해결 과정을 상세히 설명하며, 최종적으로 경로 문제를 해결한 방법을 공유합니다.
문제 현상
- Hugo 템플릿에서
iframe
을 사용하여 변환된 Marp 슬라이드를 삽입하려 할 때, 404This page could not be found 에러 발생 src
경로가 다음과 같이 잘못 생성됨
<iframe src=/slides%5cc%5co%5cn%5ct%5ce%5cn%5ct%5cethercat-presentation.html width=100% height=800 frameborder=0></iframe>
현상의 주요 특징
src
경로가 URL 인코딩되어%5c
(백슬래시) 형태로 표시.- 슬라이드 HTML 파일은
static/slides
디렉토리에 정상적으로 생성되었음에도 불구하고iframe
이 올바른 파일을 로드하지 못함. - 결과적으로 브라우저에서 슬라이드가 표시되지 않음.
문제 발생 원인
Hugo의 경로 처리
- Hugo는 파일 경로를 Windows 스타일(
\
)로 반환하며, 이는 웹 브라우저가 이해할 수 없는 백슬래시가 포함된 URL을 생성함. - 특히
.File.Dir
과 같은 Hugo 템플릿 함수는 운영 체제의 파일 경로 형식을 그대로 반환하므로, Windows 환경에서는 이 문제가 발생함.
URL 인코딩
- Hugo 템플릿에서 생성된 경로가 브라우저로 전달되는 과정에서 백슬래시(
\
)가%5c
로 URL 인코딩됨. - 이는 웹 경로가 슬래시(
/
)만 지원하기 때문에 발생
계산 방식의 제한
- Hugo 템플릿 코드에서
.File.Dir
와.File.BaseFileName
을 조합해 경로를 생성할 때, 백슬래시를 슬래시로 변환하거나 불필요한 부분을 제거하는 작업이 불완전함(?)
문제 해결 과정
템플릿에서 경로 처리 로직 수정
- Hugo 템플릿에서
replace "\\" "/"
와 같은 변환 작업을 통해 경로 수정
<iframe
src="/slides{{ .File.Dir | replace "content" "" | replace "\\" "/" | lower }}{{ .File.BaseFileName | lower }}.html"
width="100%"
height="800"
frameborder="0">
</iframe>
- 여러 번의 시도에도 백슬래시 문제가 완전히 해결되지 않음.
.RelPermalink
활용
- Hugo의
.RelPermalink
는 파일의 상대 경로를 자동으로 생성해 주는 함수로, 운영 체제의 경로 형식과 무관하게 URL 형태를 반환함 - 이를 사용하여 경로를 생성하려 했지만, 템플릿 상에서
.md
확장자를.html
로 변환하는 작업이 여전히 필요함을 확인.
<iframe
src="/slides{{ .RelPermalink | replace ".md" ".html" }}"
width="100%"
height="800"
frameborder="0">
</iframe>
- 이 접근 방식은
.RelPermalink
가 슬래시(/
)를 보장했으나, 변환 과정에서 Markdown 파일의 상대 경로만 처리할 수 있어 모든 케이스를 커버하지 못함
최종 해결 방안
메타데이터에 명시적 경로 추가
- 마지막으로, 템플릿과 경로 계산 문제를 완전히 제거하기 위해 Markdown 메타데이터에 슬라이드 경로를 명시적으로 추가하는 방법을 채택
- Python 스크립트를 수정하여 변환된 HTML 파일 경로를 자동으로 메타데이터에 삽입하도록 설정
Python 스크립트를 통해 Marp로 변환된 HTML 파일의 경로를 계산하고, 이를 slide_path
라는 메타데이터 항목으로 Markdown 파일에 추가함.
Python 스크립트
def parse_and_update_yaml_header(file_path, slide_path):
"""Markdown 파일의 YAML 헤더를 파싱하고 slide_path를 추가"""
with open(file_path, "r", encoding="utf-8") as f:
lines = f.readlines()
if lines and lines[0].strip() == "---":
yaml_lines = []
for line in lines[1:]:
if line.strip() == "---":
break
yaml_lines.append(line)
metadata = yaml.safe_load("\n".join(yaml_lines))
metadata['slide_path'] = slide_path # slide_path 추가
new_metadata = yaml.dump(metadata, default_flow_style=False).strip()
rest_of_content = "".join(lines[len(yaml_lines) + 2:])
# 파일 갱신
with open(file_path, "w", encoding="utf-8") as f:
f.write(f"---\n{new_metadata}\n---\n{rest_of_content}")
변환된 경로는 /slides/...
형식으로 작성되며, Markdown 파일의 메타데이터에 추가됨.
Markdown 파일 예시
---
title: EtherCAT Presentation
type: slide
slide_path: /slides/ethercat/ethercat-presentation.html
---
Hugo 템플릿 : Single.html
{{ define "main" }}
<div class='hx-mx-auto hx-flex {{ partial "utils/page-width" . }}'>
<article class="hx-w-full hx-break-words hx-flex hx-min-h-[calc(100vh-var(--navbar-height))] hx-min-w-0 hx-justify-center hx-pb-8 hx-pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx-w-full hx-min-w-0 hx-max-w-6xl hx-px-6 hx-pt-4 md:hx-px-12">
<br class="hx-mt-1.5 hx-text-sm" />
<h1 class="hx-text-center hx-mt-2 hx-text-4xl hx-font-bold hx-tracking-tight hx-text-slate-900 dark:hx-text-slate-100">
{{ .Title }}
</h1>
{{ if eq .Params.type "slide" }}
<div class="marp-slide-container">
<iframe
src="{{ .Params.slide_path }}"
width="100%"
height="800"
frameborder="0"
class="hx-w-full hx-shadow-md">
</iframe>
</div>
{{ else }}
<div class="content">
{{ .Content }}
</div>
{{ end }}
</main>
</article>
</div>
{{ end }}