@m1kapp/seo
v0.0.1
Published
SEO & OG image utilities for m1kapp projects
Readme
@m1kapp/seo
Next.js / Vercel OG 이미지를 5줄로 만드는 라이브러리.
Pretendard 폰트 내장 · 7가지 템플릿 · 다크/그라데이션 배경
설치
npm install @m1kapp/seo빠른 시작
// app/og/route.tsx
import { OG, loadPretendard } from "@m1kapp/seo";
import { ImageResponse } from "next/og";
export const runtime = "nodejs";
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const fonts = await loadPretendard();
return new ImageResponse(
<OG
type="default"
title={searchParams.get("title") ?? "My App"}
sub="한 줄 설명"
appName="MyApp"
domain="myapp.com"
color="#007B5F"
/>,
{
width: 1200,
height: 630,
fonts,
emoji: "twemoji",
headers: {
"Cache-Control": "public, s-maxage=86400, stale-while-revalidate=604800",
},
}
);
}템플릿
| type | 설명 | 출력 크기 |
|------|------|-----------|
| default | 제목 + 서브타이틀 | 1200×630 |
| article | 작성자·날짜·카테고리 포함 아티클 | 1200×630 |
| stat | 숫자/지표를 크게 강조 | 1200×630 |
| product | 제품명·태그라인·특징 목록 | 1200×630 |
| match | 홈/원정팀 + 스코어 | 1200×630 |
| square | 정사각형 SNS 썸네일 | 1200×1200 |
| icon | 앱 아이콘/파비콘 | 512×512 |
Default
<OG type="default" title="제목" sub="서브텍스트" badge="🚀 NEW" appName="App" domain="app.com" />Article
<OG
type="article"
title="Next.js 15에서 달라진 것들"
author="minho"
date="2026. 4. 15"
category="📝 개발"
appName="Blog"
domain="blog.com"
/>Stat
<OG type="stat" stat="10,000+" label="개발자가 사용 중" sub="매달 2배씩 성장" badge="🏆 Milestone" appName="App" />Product
<OG
type="product"
title="@m1kapp/seo"
tagline="OG 이미지, 5줄로 끝내세요"
features={["7가지 템플릿", "Pretendard 내장", "다크/그라데이션 배경"]}
badge="✨ v1.0"
appName="m1k"
/>Match
<OG type="match" home="서울 FC" away="부산 아이파크" score="2:1" sub="⚽ K리그 1" badge="🔴 LIVE" appName="Watchpoint" />Square / Icon
<OG type="square" title="제목" sub="설명" appName="App" />
<OG type="icon" appName="App" color="#007B5F" radius={96} />공통 props (OGConfig)
| prop | 타입 | 설명 |
|------|------|------|
| appName | string | 좌상단 앱 이름 |
| color | string (hex) | 브랜드 색상 |
| domain | string | 하단 도메인 |
| bg | "dark" \| "gradient" \| "blend" | 배경 스타일 (기본: "dark") — 다크 / 다크 그라디언트 / 그라디언트 |
| logoUrl | string | 로고 이미지 URL (지정 시 첫 글자 대신 이미지 표시) |
배경 스타일
// 다크 (기본)
<OG type="default" title="제목" bg="dark" ... />
// 다크 그라디언트 (aurora blob 효과, 흰 글씨)
<OG type="default" title="제목" bg="gradient" ... />
// 그라디언트 (라이트 파스텔 + 3색 강렬 대비, 흰 글씨)
<OG type="default" title="제목" bg="blend" ... />폰트
Pretendard (기본)
import { loadPretendard } from "@m1kapp/seo";
const fonts = await loadPretendard(); // 기본: weight 700, 900
const fonts = await loadPretendard([400, 700, 900]); // weight 직접 지정커스텀 폰트 URL
import { loadFont } from "@m1kapp/seo";
const font = await loadFont({
name: "MyFont",
url: "https://example.com/MyFont-Bold.otf",
weight: 700,
});Google Fonts
import { loadGoogleFont } from "@m1kapp/seo";
const fonts = await loadGoogleFont("Noto Sans KR", [400, 700]);캐시 수동 갱신
CDN에 캐싱된 OG 이미지를 즉시 갱신하려면 URL에 v 파라미터를 추가하세요.
import { getOGVersion } from "@m1kapp/seo";
// 갱신이 필요할 때만 호출
const freshUrl = `/og?title=${title}&v=${getOGVersion()}`;
// → /og?title=...&v=20260415152347이모지
ImageResponse의 emoji 옵션으로 이모지 스타일을 지정하세요.
import type { EmojiStyle } from "@m1kapp/seo";
new ImageResponse(element, {
emoji: "twemoji", // "twemoji" | "openmoji" | "noto" | "fluent" | "blobmoji"
});raw Satori를 사용한다면 createEmojiLoader()를 활용하세요.
import satori from "satori";
import { createEmojiLoader } from "@m1kapp/seo";
const svg = await satori(<OG ... />, {
width: 1200, height: 630,
fonts,
loadAdditionalAsset: createEmojiLoader(),
});라이선스
MIT
