next/og로 파비콘·OG 이미지 생성하는 방법을 쓰고 나서, 왜 하필 이런 형태의 코드가 됐는지 궁금해서 더 파봤어요. img-compressormedia-crop 두 저장소의 icon.tsx, opengraph-image.tsx에서 반복되는 패턴을 제약 쪽에 초점 맞춰서 볼게요.

왜 두 프로젝트 다 edge 런타임을 명시했나

두 저장소의 아이콘·OG 이미지 파일은 다 export const runtime = 'edge';를 첫 줄 근처에 선언해요. ImageResponse는 Satori라는 JSX-투-SVG 변환기를 내부적으로 쓰는데, 이게 Node.js 전용 파일시스템 API나 네이티브 모듈 없이도 edge 환경에서 돌아가게 설계돼있더라고요. 런타임을 명시적으로 선언해두면, 배포 플랫폼이 이 함수를 Node 서버리스가 아니라 edge 함수로 다뤄야 한다는 게 코드만 봐도 확실해져요.

진짜 DOM이 아니라 제한된 flexbox 서브셋이었어요

두 프로젝트 아이콘 코드를 보면 display: 'flex', alignItems, justifyContent 조합만 계속 반복해서 쓰고, CSS Grid나 gap, 외부 스타일시트 클래스는 전혀 안 나와요. ImageResponse에 넘기는 JSX는 실제 브라우저가 그리는 게 아니라 Satori가 해석하는 별도 레이아웃 엔진을 거치기 때문이더라고요. 이 엔진이 지원하는 CSS 범위는 일반 브라우저보다 좁고, 특히 폰트는 시스템 폰트가 아니라 명시적으로 등록한 폰트 파일만 쓸 수 있어요.

이 제약이 코드 스타일에 남긴 흔적

두 프로젝트 다 이모지나 인라인 SVG처럼 폰트에 안 기대는 요소를 아이콘 핵심 그래픽으로 쓰고 있어요. img-compressor는 🖼️ 이모지를, media-crop은 stroke 속성으로 직접 그린 SVG 패스를 쓰고요. 텍스트 로고보다 이모지나 벡터 패스를 고른 건, ImageResponse 환경에서 커스텀 폰트 로드·등록 과정을 아예 생략할 수 있어서였을 거라고 짐작해요. 다만 이건 코드에 이유가 직접 적혀있는 게 아니라, 두 프로젝트 실제 구현에서 관찰한 패턴을 보고 제가 추정한 거예요.