logo hsb.horse
← 블로그 목록으로 돌아가기

블로그

브라우저에서 Google Fonts에서 WOFF 파일을 가져 오는 데 실패한 비망록

클라이언트 측에서 Google Fonts에서 ttf/otf/woff 파일을 가져와 @vercel/satori에서 사용하려고 시도한 시행 착오 기록. User-Agent 헤더 제한 및 woff2 비대응 문제에 대한 정리.

게시일:

브라우저에서 vercel / satori를 사용하여 이미지를 생성하고 싶었습니다.

폰트 파일을 셀프 호스팅하면 문제없이 satori를 이용할 수 있지만, 가능하면 셀프 호스팅하고 싶지 않다(대응 폰트가 늘어나면 번거로움).

Google Font를 통해 글꼴 파일을 다운로드하고 satori에서 사용할 수 없는지 시도했지만 결론부터 말하면 실패했습니다.

결론

satori가 woff2에 대응하고 있으면 실현할 수 있지만, 대응하고 있지 않는 현상 프런트 엔드(클라이언트 측)만으로는 실현할 수 없다.

서버 측이 있으면 실현할 수 있다.

요구 사항

  • 서버측 없음
  • 사용자가 Google Fonts에서 사용하려는 글꼴의 이름과 스타일을 선택하고 입력란에 입력한다고 가정

예상되는 처리 흐름

  1. 사용자가 글꼴 이름과 스타일을 입력
  2. Google Fonts API에서 CSS 얻기
  3. CSS에서 글꼴 파일 URL 추출
  4. 폰트 파일 다운로드
  5. satori에서 이미지 생성

CSS Fetch

Google Font는 유저 에이전트에 맞추어, 반환하는 폰트 파일의 형식이 바뀌므로 User-Agent 헤더를 재기록하는 것으로 해결을 시도했다.

하지만 잘 생각하면 금지 헤더이므로 덮어쓸 수 없고, 눈론은 여기에서 좌절한다.

export const USER_AGENTS_BY_FONT_TYPE = {
// IE11 as woff
woff: "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
// Googlebot as truetype
ttf: "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
};
export type FontType = keyof typeof USER_AGENTS_BY_FONT_TYPE;
export async function fetchGoogleFontCSS(
url: string,
fontType: FontType = "ttf"
) {
const response = await fetch(url, {
headers: {
"User-Agent": USER_AGENTS_BY_FONT_TYPE[fontType],
},
});
if (!response.ok) {
throw new Error(
`Failed to load font: ${response.status} ${response.statusText}`
);
}
return response.text();
}

User-Agent는 금지 헤더이므로 fetch로 덮어쓸 수 없다.

CSS에서 데이터 추출

만일 CSS가 취득할 수 있었을 경우의 구현은 이하와 같다.

const subsetRegex = /\/\* (.+?) \*\//;
const fontFileRegex = /src: url\((.+?)\)/;
type ExtractResult = {
subsets: Record<string, string>;
hasItems: boolean;
};
const extract = (regex: RegExp, line: string) => regex.exec(line)?.[1];
export function extractFontFromCSS(css: string): ExtractResult {
const subsets: Record<string, string> = {};
let currentSubset = "";
for (const line of css.split("\n")) {
const subset = extract(subsetRegex, line);
if (subset) {
currentSubset = subset;
continue;
}
const url = extract(fontFileRegex, line);
if (url && !subsets[currentSubset]) {
subsets[currentSubset] = url;
}
}
return {
subsets,
hasItems: Object.keys(subsets).length > 0,
};
}

끝에

그래서 결국 서버 사이드 또는 프록시가 필요하다는 결론으로 ​​끝.

브라우저로부터의 제약(금지 헤더, woff2만 전달)에 의해, 클라이언트 측만으로의 실현은 단념했다.

참고 자료

요약

브라우저에서 Google Fonts로부터 woff/ttf를 취득하려고 했지만, User-Agent 제한과 satori의 woff2 비대응에 의해 실패.

서버 측 또는 프록시를 통과해야합니다. 실패로부터 배운 지견으로서 기록을 남겨둔다.