같은 틀에 내용만 바뀌는 이미지를 스무 장 만들 때, 시간은 디자인이 아니라 반복에서 사라집니다. 한 장을 HTML로 제대로 만들어 두면 나머지는 값을 갈아 끼우는 일이 됩니다.
한 장을 만드는 속도는 편집기가 빠릅니다. HTML이 앞서는 지점은 장수가 늘고 수정이 반복될 때, 대체로 같은 레이아웃을 열 장 넘게 찍어낼 때부터입니다.
사진 보정과 합성은 편집기의 영역입니다. 표·스펙·가격처럼 규칙적인 블록이 반복되는 상세페이지와 카드뉴스가 HTML이 강한 자리입니다.
3번을 건너뛰면 재작업의 대부분이 생깁니다. 변환은 실제 브라우저 렌더링이라, 브라우저에서 어긋난 것은 결과에서도 어긋납니다.
권장 가로 폭, 한 장당 최대 세로, 파일당 용량 상한, 허용 형식. 이 네 가지는 채널마다 다르고 예고 없이 바뀝니다. 그래서 이 문서에는 숫자를 적지 않습니다. 판매하는 채널의 판매자 도움말에서 직접 확인해 적어 두십시오.
| 확인할 값 | 무엇을 결정하는가 |
|---|---|
| 권장 가로 폭 | 변환 설정 폭, 글자 크기와 여백의 기준 |
| 한 장당 최대 세로 | 몇 조각으로 나눌지 |
| 파일당 용량 상한 | PNG로 올릴지, JPEG로 다시 저장할지 |
| 허용 형식 | 변환 결과를 그대로 쓸 수 있는지 |
네이버 스마트스토어는 판매자센터의 상품 등록 도움말, 쿠팡은 쿠팡 윙 헬프센터, 11번가는 셀러오피스 매뉴얼, 카페24는 헬프센터가 이 값을 안내합니다. "상품 이미지"나 "상세설명" 항목을 찾으면 됩니다. 자체 쇼핑몰이라면 채널이 정한 상한이 없으니 본문 영역의 실제 폭에 맞춥니다.
변환 설정에 넣는 폭은 CSS 픽셀이고 저장은 2배 밀도로 이뤄집니다. 그래서 가로로 넘치는 요소만 없다면 출력 가로 = 설정 폭 × 2입니다. 설정 폭은 100에서 4000 사이로 제한되므로 한 장으로 낼 수 있는 가로는 8000px이 최대입니다. 도움말의 숫자가 표시 폭인지 픽셀 상한인지에 따라 넣을 값이 달라집니다.
표시 폭 기준을 권합니다. CSS에 적은 font-size: 16px이 화면에서 실제 16px 크기가 되어, 가독성을 눈대중이 아니라 숫자로 판단할 수 있습니다. 픽셀 상한에 걸리면 변환한 뒤 축소하고, 용량 때문에 JPEG로 다시 저장할 때는 축소를 먼저 해야 압축 흔적이 확대되지 않습니다. 크기 계산은 해상도와 크기 이해하기에 정리해 두었습니다.
세로에는 반대 방향의 함정이 있습니다. 렌더링을 시작할 때의 창 높이가 800 CSS px이고 문서 높이는 그보다 작아질 수 없어서, 세로가 1600px보다 짧은 PNG는 나오지 않습니다. 내용이 적은 카드뉴스 한 장이나 작은 배지를 만들면 아래쪽에 배경색만 남은 띠가 그대로 붙어 나옵니다. 정사각형처럼 비율이 정해진 이미지는 내용 높이가 800 CSS px을 넘도록 폭과 여백을 잡거나, 받은 뒤 아래를 잘라내야 합니다.
바뀔 자리에 표식을 심습니다. 본문에 우연히 나타나지 않을 형태여야 하므로 {{ }}로 감싸는 편이 안전합니다. 상품명 자리에 그냥 상품명이라고 적으면 찾아 바꾸기가 설명 문장 속의 같은 단어까지 바꿔버립니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>{{name}}</title>
<!-- 이 주석의 값이 결과 파일 이름이 됩니다 -->
<!-- 상품 코드: {{code}} -->
<style>
:root { --accent: #1f6feb; --ink: #16181d; }
* { margin: 0; padding: 0; box-sizing: border-box; }
/* 기준 폭 860 — 변환 설정 폭에도 860을 넣습니다 */
body { width: 100%; background: #fff; color: var(--ink);
font-family: "Noto Sans CJK KR", "NanumGothic", sans-serif; }
.wrap { padding: 64px 56px; }
.badge { display: inline-block; padding: 6px 14px; border-radius: 999px;
background: var(--accent); color: #fff; font-size: 15px; font-weight: 700; }
h1 { margin: 20px 0 12px; font-size: 40px; line-height: 1.3; font-weight: 800; }
.desc { font-size: 18px; line-height: 1.8; color: #4a4f57; }
.price { margin-top: 28px; font-size: 32px; font-weight: 800; }
.price span { font-size: 18px; font-weight: 500; color: #6b7280; }
</style>
</head>
<body>
<div class="wrap">
<span class="badge">{{badge}}</span>
<h1>{{name}}</h1>
<p class="desc">{{desc}}</p>
<p class="price">{{price}}원 <span>부가세 포함</span></p>
</div>
</body>
</html>이 뼈대에서 두 줄이 특히 자주 어긋납니다. 하나는 폭입니다. body에 width: 860px처럼 픽셀을 못 박아 두고 설정 폭에 1000을 넣으면 오른쪽 140 CSS px이 빈 배경으로 남고, 반대로 600을 넣으면 잘리는 것이 아니라 넘친 폭 860을 기준으로 가로 1720px짜리 PNG가 저장됩니다. 캡처가 창 밖으로 넘친 부분까지 담기 때문입니다. 어느 쪽이든 장마다 규격이 달라지므로, 바깥 상자는 width: 100%로 두고 기준 폭은 주석으로만 남긴 뒤 변환 설정에 그 숫자를 넣으십시오. 안쪽 여백과 글자 크기는 px로 고정합니다.
다른 하나는 폰트 이름입니다. 렌더링 서버에 설치된 이름은 "Noto Sans CJK KR"이고, Google Fonts에서 쓰는 "Noto Sans KR"과는 다른 문자열입니다. 웹폰트로 따로 불러오지 않은 채 뒤엣것만 적으면 그 이름은 아무것도 가리키지 못한 채 다음 후보로 넘어갑니다. <html lang="ko">도 함께 필요합니다. 이름으로 찾지 못한 글자를 대신 그릴 폰트를 Chromium이 문서 언어로 고르는데, 언어 표시가 없으면 한글이 일본어판 자형으로 나올 수 있기 때문입니다. 설치된 폰트 목록과 웹폰트를 얹는 방법은 한글 폰트 가이드에 정리해 두었습니다.
파일을 복제한 뒤 표식을 값으로 치환합니다. 에디터의 일괄 치환이면 충분합니다.
값이 늘면 표로 관리하는 편이 낫습니다. 엑셀에서 CSV로 저장한 뒤 한 줄마다 HTML 하나를 만듭니다. 파이썬 기본 기능만 쓰므로 추가 설치가 없습니다.
# data.csv (엑셀에서 "CSV UTF-8"로 저장)
code,badge,name,desc,price
SUMMER01,신상품,여름 린넨 셔츠,통기성이 좋은 여름용 린넨 셔츠입니다.,39000
SUMMER02,베스트,코튼 와이드 팬츠,활동량이 많은 날에 편한 와이드 핏입니다.,45000# make.py — template.html + data.csv → out/*.html
import csv, html, pathlib
template = pathlib.Path("template.html").read_text(encoding="utf-8")
outdir = pathlib.Path("out")
outdir.mkdir(exist_ok=True)
with open("data.csv", encoding="utf-8-sig", newline="") as f:
for i, row in enumerate(csv.DictReader(f), start=1):
if row.get("price", "").isdigit(): # 39000 -> 39,000
row["price"] = f"{int(row['price']):,}"
page = template
for key, value in row.items():
page = page.replace("{{" + key + "}}", html.escape(value or ""))
name = f"{i:02d}-{row['code']}.html"
(outdir / name).write_text(page, encoding="utf-8")
print("생성:", name)python3 make.py 한 번이면 out 폴더에 전부 만들어집니다. html.escape를 거치므로 설명에 &나 부등호가 있어도 레이아웃이 깨지지 않고, utf-8-sig는 엑셀이 붙이는 BOM을 걷어냅니다. 이 처리를 빼면 첫 열 이름 앞에 보이지 않는 문자가 붙어 code 열을 찾지 못하고 스크립트가 KeyError로 멈춥니다. 천 단위 쉼표 같은 표시용 가공도 여기서 처리하는 편이 낫습니다. 표에는 계산할 수 있는 원본 숫자를 두고 보기 좋은 형태는 만들 때 붙이면, 값이 바뀌어도 고칠 곳이 표 한 군데로 유지됩니다.
HTML 안에 <!-- 상품 코드: SUMMER01 --> 주석을 넣어두면 결과 파일 이름이 SUMMER01.png가 되어, 변환 결과와 상품이 곧바로 대응됩니다.
ltm-원본파일명.png으로 되돌아갑니다.01-메인.html처럼 번호를 붙이면 이름순 정렬이 그대로 순서가 됩니다.여기서 놓치기 쉬운 것은 결과 이름이 원본 파일 이름이 아니라 코드에서 나온다는 점입니다. 앞의 스크립트가 붙인 01- 번호는 입력 파일에만 남고, 코드가 있으면 결과는 SUMMER01.png가 됩니다. 순서를 결과에도 남기고 싶다면 번호를 코드 안에 넣어 01_SUMMER01처럼 쓰십시오. 변환이 끝나면 ZIP으로 한 번에 받고, 업로드 파일과 결과 이미지는 약 1시간 뒤 자동 삭제되므로 받은 즉시 보관하십시오.
편집 화면에서는 확대해 보기 때문에 거의 다 읽힙니다. 문제는 구매자가 보는 크기입니다. 결과를 실제 표시 폭으로 축소해 놓고 휴대폰으로 확인하십시오. 표시 폭 기준으로 설계했다면 CSS의 px 값이 곧 체감 크기이므로, 본문이 16px 안팎보다 작아지지 않는지 숫자로도 점검할 수 있습니다.
웹 접근성 지침(WCAG)의 명도 대비 기준은 본문 4.5:1 이상, 큰 텍스트 3:1 이상입니다. 여기서 큰 텍스트는 18pt(약 24px) 이상이거나 굵은 14pt(약 18.66px) 이상을 말합니다. 이 기준은 글자를 그려 넣은 이미지에도 똑같이 적용되므로 상세페이지 문구라고 예외가 되지 않습니다. 실무에서 걸리는 자리는 대개 둘입니다. 흰 배경 위의 옅은 회색 글자는 회색을 한 단계 어둡게 하면 해결되고, 사진 위에 그냥 얹은 흰 글자는 사진과 글자 사이에 반투명한 어두운 층을 한 겹 깔아야 사진을 바꿔도 대비가 유지됩니다.
.hero {
position: relative;
padding: 72px 48px;
/* 상대 경로는 변환 서버에서 열리지 않습니다.
실제 https 주소나 base64 data URI 를 넣으십시오 */
background: url("https://example.com/photo.jpg") center / cover no-repeat;
}
/* 사진 위에 어두운 층을 깔아 글자 대비를 확보합니다 */
.hero::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(0,0,0,.15), rgba(0,0,0,.6));
}
.hero h2 {
position: relative; /* 어두운 층보다 위로 올립니다 */
color: #fff;
}업로드된 이미지는 썸네일로 잘리거나 모서리가 둥글게 처리될 수 있습니다. 중요한 글자와 로고는 최소한 본문 한 줄 높이만큼 안쪽으로 들여놓으십시오.
할인가를 빨간색으로만, 품절을 회색으로만 표시하면 색각 이상이 있는 구매자나 밝은 야외에서는 구분이 사라집니다. 색은 강조로만 쓰고 "할인", "품절" 같은 글자나 취소선을 함께 넣으십시오.
폰트가 의도한 대로 나왔는지, 사진 자리가 비지 않았는지, 오른쪽이 잘리지 않았는지 봅니다. 사진이 통째로 비었다면 경로 문제입니다. 업로드된 HTML은 서버의 임시 위치에서 열려 같은 폴더의 상대 경로 파일을 찾지 못하므로, 절대 https:// 주소나 base64로 넣어야 합니다. 자세한 내용은 경로와 리소스 처리에 있습니다.
모든 내용을 담은 세로로 아주 긴 이미지 한 장은 만들기는 편하지만 거의 항상 손해입니다.
나누는 기준은 파일 크기가 아니라 내용의 단위입니다. 상단 후킹, 핵심 소구점, 상세 스펙, 사이즈 표, 배송·교환 안내처럼 의미가 끝나는 자리에서 끊고 문단이나 표 중간은 피하십시오. 세로 상한에 아슬아슬하게 맞추면 문구 한 줄만 늘어도 넘칩니다.
경계가 보이지 않게 하려면 조각마다 같은 설정 폭으로 변환하고, 좌우 여백과 배경색을 동일하게 유지하며, 조각의 위아래 끝에 그림자나 둥근 모서리를 두지 않으면 됩니다. 공통 스타일은 각 파일의 <style> 안에 복사해 넣으십시오. 옆에 둔 외부 CSS 파일은 함께 올라가지 않습니다.
배송 정책이 바뀌어도 해당 조각만 다시 변환해 교체하면 됩니다. 분할의 진짜 이득은 유지보수에 있습니다.
완성된 이미지는 사람 눈에는 글자로 보이지만 기계에는 그림입니다. 그 안의 문장은 선택도 복사도 되지 않고, 채널 안의 검색과 브라우저 번역, 스크린리더의 대상도 아닙니다. 검색엔진 역시 그 문구를 상품 설명으로 인식하지 않습니다.
국내 오픈마켓은 공정거래위원회의 「전자상거래 등에서의 상품 등의 정보제공에 관한 고시」에 따라 품목별 필수 표기 항목의 입력란을 따로 두고 있습니다. 원산지, 제조자, 소재, 크기, 품질보증 기준, 교환·반품 조건 같은 항목은 이미지에 그려 넣는 것이 아니라 그 입력란에 텍스트로 채워야 합니다. 필수 항목은 품목마다 다르므로 등록 화면의 고시 항목과 고시 원문을 확인하십시오.
이미지는 설득을, 입력란은 고지를 맡기는 편이 안전합니다. 이미지에 같은 내용을 함께 담는 것은 좋지만, 이미지에만 담아서는 안 됩니다.
같은 이유로 자주 바뀌는 값은 이미지에 굽지 마십시오. 가격, 재고, 배송비, 이벤트 기간을 이미지에 넣으면 값이 바뀔 때마다 다시 만들어 올려야 하고, 이미지와 실제 판매 조건이 어긋나는 순간 그 자체가 분쟁의 소지가 됩니다.
body 폭, 채널 권장 폭과 일치합니까?뼈대부터 만들기 부담스럽다면 HTML 템플릿 작성 가이드에 그대로 복사해 쓸 수 있는 상세페이지 템플릿을 두었습니다.