fix: Pretendard 미로드로 @fox 컴포넌트가 OS 기본 서체로 그려지던 문제 수정
@fox 토큰이 패밀리명을 리터럴 `Pretendard`로 지정하는데 프로젝트 어디에도 그 이름의 폰트 공급이 없었다. 그래서 모든 @fox 컴포넌트가 OS 기본 서체(맥 Apple SD Gothic Neo / 윈도 맑은 고딕)로 갈라져 그려졌고, 글자 폭이 시안과 어긋났다. - public/fonts: Pretendard v1.3.9 woff2 400/600/800 반입(OFL 1.1). 토큰이 쓰는 weight가 정확히 이 셋이다 — 특히 800은 wantedSans에 없어 가짜 굵기로 합성되고 있었다. - globals.scss: @font-face로 직접 선언. next/font/local은 패밀리명을 해시로 바꿔 토큰의 리터럴과 만나지 못해 이 용도로는 쓸 수 없다. - globals.scss: body에 `--app-font-sans` 연결. 이 계약 변수를 읽는 곳이 없어 wantedSans가 로드만 되고 어디에도 적용되지 않았다. 이제 @fox 밖 텍스트가 wantedSans를 쓴다. - next.config.ts: `/fonts/*`를 no-store에서 제외. public/ 자산이라 그대로 두면 페이지를 열 때마다 2.2MB를 다시 받는다. - proxy.ts: 폰트 확장자를 matcher에서 제외. 세션 없는 방문자의 폰트 요청이 `/login`으로 리다이렉트돼 로그인 화면만 대체 서체로 그려졌다. - @fox/README.md: "호스트 앱과의 계약"이 @fox 컴포넌트도 `--app-font-sans`를 읽는 것처럼 적혀 있었으나 사실이 아니다. 실제 요구사항(Pretendard @font-face·400/600/800)으로 교체. FoxCarouselIndicator number형 56.92px → 59.69px. 시안 62px과의 잔여 2.31px은 Figma가 텍스트 상자 폭을 정수로 올리고(+1.57) 마지막 글자의 음수 자간을 빼지 않는(+0.75) 표기 차이다. Co-Authored-By: Claude Opus 5
@2d7a4a8e0be5958d9f4f91ad13f1a2c2baaeabec
--- @fox/README.md
+++ @fox/README.md
... | ... | @@ -61,7 +61,8 @@ |
| 61 | 61 |
{ "compilerOptions": { "paths": { "@fox/*": ["./@fox/*"] } } }
|
| 62 | 62 |
``` |
| 63 | 63 |
|
| 64 |
-6. (선택) 폰트를 주입합니다 — 아래 "호스트 앱과의 계약" 참고. |
|
| 64 |
+6. **폰트를 공급합니다** — 아래 "호스트 앱과의 계약" 참고. 빠뜨리면 컴포넌트가 OS 기본 |
|
| 65 |
+ 서체로 그려져 글자 폭이 시안과 어긋납니다. |
|
| 65 | 66 |
|
| 66 | 67 |
## React 없이 쓰기 |
| 67 | 68 |
|
... | ... | @@ -220,15 +221,25 @@ |
| 220 | 221 |
|
| 221 | 222 |
## 호스트 앱과의 계약 |
| 222 | 223 |
|
| 223 |
-`@fox`가 소유하지 않고 앱에서 받는 값입니다. |
|
| 224 |
+**폰트 파일은 앱이 소유합니다.** `@fox`는 폰트를 담고 있지 않으므로, 앱이 아래를 공급하지 |
|
| 225 |
+않으면 브라우저 기본 서체로 그려집니다. |
|
| 224 | 226 |
|
| 225 |
-| CSS 변수 | 용도 | 미주입 시 | |
|
| 227 |
+| 앱이 공급할 것 | 쓰이는 곳 | 미공급 시 | |
|
| 226 | 228 |
| --- | --- | --- | |
| 227 |
-| `--app-font-sans` | 기본 서체 | `system-ui`로 폴백 | |
|
| 228 |
-| `--app-font-mono` | 고정폭 서체 | `ui-monospace`로 폴백 | |
|
| 229 |
+| `@font-face { font-family: "Pretendard" }` | **`@fox` 컴포넌트 전부** | OS 기본 서체(맥 Apple SD Gothic Neo / 윈도 맑은 고딕)로 갈라짐 |
|
|
| 230 |
+| `--app-font-sans` | `@fox` 밖 앱 텍스트 | 앱이 알아서 폴백 | |
|
| 231 |
+| `--app-font-mono` | 고정폭이 필요한 앱 텍스트 | 앱이 알아서 폴백 | |
|
| 229 | 232 |
|
| 230 |
-폰트 파일은 앱이 소유합니다. `@fox`는 변수를 참조만 하므로, 복사해 간 프로젝트는 그 |
|
| 231 |
-프로젝트의 폰트를 그대로 씁니다. |
|
| 233 |
+> ⚠️ `@fox` 컴포넌트의 서체는 `--app-font-sans`가 **아닙니다.** 토큰 |
|
| 234 |
+> (`tokens/_primitive.scss`의 `font.family.*`)이 패밀리명을 리터럴 `Pretendard`로 못 박고 |
|
| 235 |
+> 있어, 그 이름의 `@font-face`를 앱이 선언해 줘야 합니다. 폰트를 바꾸려면 Figma 원본 토큰을 |
|
| 236 |
+> 고쳐 재생성하는 수밖에 없습니다 — 즉 이 지점만은 이식성이 없습니다. |
|
| 237 |
+> |
|
| 238 |
+> 필요한 굵기는 토큰이 쓰는 **400 / 600 / 800** 세 개입니다. 800을 빼면 `font-weight(bold)`가 |
|
| 239 |
+> 가짜 굵기로 합성됩니다. |
|
| 240 |
+> |
|
| 241 |
+> `next/font/local`은 패밀리명을 해시(`__Pretendard_xxxx`)로 바꾸므로 이 용도로는 쓸 수 |
|
| 242 |
+> 없습니다. 이 저장소는 `app/globals.scss`에 `@font-face`를 직접 선언합니다. |
|
| 232 | 243 |
|
| 233 | 244 |
## 새 컴포넌트 추가 |
| 234 | 245 |
|
--- app/globals.scss
+++ app/globals.scss
... | ... | @@ -10,6 +10,35 @@ |
| 10 | 10 |
// 그래야 React 밖(다른 프레임워크·순수 SCSS)에서도 같은 방식으로 쓸 수 있다. |
| 11 | 11 |
@use "@fox/styles/components"; |
| 12 | 12 |
|
| 13 |
+// Pretendard — @fox 토큰이 패밀리명을 **리터럴 `Pretendard`로** 지정하므로(`font.family.*`), |
|
| 14 |
+// 앱은 정확히 그 이름의 폰트를 공급해야 한다. `next/font/local`은 패밀리명을 |
|
| 15 |
+// 해시(`__Pretendard_xxxx`)로 바꿔 그 리터럴과 영영 만나지 못해 쓸 수 없다 — 토큰은 Figma |
|
| 16 |
+// export 자동 생성이라 이름을 코드 쪽에 맞추는 반대 방향도 불가능하다. |
|
| 17 |
+// weight는 토큰이 실제로 쓰는 400·600·800 셋뿐이다(`weight-regular`/`medium`/`bold`). |
|
| 18 |
+@font-face {
|
|
| 19 |
+ font-family: "Pretendard"; |
|
| 20 |
+ font-style: normal; |
|
| 21 |
+ font-weight: 400; |
|
| 22 |
+ font-display: swap; |
|
| 23 |
+ src: url("/fonts/pretendard-regular.woff2") format("woff2");
|
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+@font-face {
|
|
| 27 |
+ font-family: "Pretendard"; |
|
| 28 |
+ font-style: normal; |
|
| 29 |
+ font-weight: 600; |
|
| 30 |
+ font-display: swap; |
|
| 31 |
+ src: url("/fonts/pretendard-semibold.woff2") format("woff2");
|
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+@font-face {
|
|
| 35 |
+ font-family: "Pretendard"; |
|
| 36 |
+ font-style: normal; |
|
| 37 |
+ font-weight: 800; |
|
| 38 |
+ font-display: swap; |
|
| 39 |
+ src: url("/fonts/pretendard-extrabold.woff2") format("woff2");
|
|
| 40 |
+} |
|
| 41 |
+ |
|
| 13 | 42 |
// 앱 셸 — 루트 레이아웃이 뷰포트 높이를 채우고 세로로 쌓이는 구조. |
| 14 | 43 |
// 푸터를 바닥에 붙이거나 본문 영역만 스크롤시키는 화면들이 이 전제에 기댄다. |
| 15 | 44 |
html, |
... | ... | @@ -21,4 +50,10 @@ |
| 21 | 50 |
display: flex; |
| 22 | 51 |
flex-direction: column; |
| 23 | 52 |
min-block-size: 100%; |
| 53 |
+ |
|
| 54 |
+ // @fox 밖 텍스트의 서체. 디자인 값이 아니라 layout.tsx가 주입하는 계약 변수를 꽂는 |
|
| 55 |
+ // 지점이라 여기 둔다 — 이 한 줄이 없으면 `--app-font-sans`를 읽는 곳이 없어 wantedSans가 |
|
| 56 |
+ // 로드만 되고 아무 데도 적용되지 않는다. @fox 컴포넌트는 각자 토큰으로 Pretendard를 |
|
| 57 |
+ // 덮어쓰므로 영향을 받지 않는다. |
|
| 58 |
+ font-family: var(--app-font-sans, system-ui), system-ui, sans-serif; |
|
| 24 | 59 |
} |
--- app/layout.tsx
+++ app/layout.tsx
... | ... | @@ -25,11 +25,10 @@ |
| 25 | 25 |
THEME_ATTRIBUTE_NAME |
| 26 | 26 |
)},t)}catch(e){}})()`;
|
| 27 | 27 |
|
| 28 |
-// 어드민 전체 기본 서체. 폰트 파일은 **앱이 소유**하고 디자인 시스템은 참조만 한다 — |
|
| 29 |
-// `@fox`는 `--app-font-sans`를 읽을 뿐이라(@fox/README.md "호스트 앱과의 계약") 폴더를 |
|
| 30 |
-// 다른 프로젝트로 복사해도 그 프로젝트의 서체를 그대로 따른다. 변수명이 `--app-*`인 것도 |
|
| 31 |
-// 그 계약의 이름이기 때문이며, 미주입 시에도 `system-ui`로 폴백하도록 토큰 쪽에 폴백이 |
|
| 32 |
-// 들어 있다. |
|
| 28 |
+// `@fox` **밖** 앱 텍스트의 기본 서체. 폰트 파일은 앱이 소유하고 `--app-font-sans` 계약으로 |
|
| 29 |
+// 넘긴다(@fox/README.md "호스트 앱과의 계약") — 그 변수를 실제로 꽂는 곳은 globals.scss의 |
|
| 30 |
+// `body`다. @fox 컴포넌트는 이 값을 쓰지 않는다: 토큰이 패밀리명을 `Pretendard`로 직접 |
|
| 31 |
+// 지정하고 그 @font-face도 globals.scss에 있다. 즉 한 화면에 두 서체가 공존한다. |
|
| 33 | 32 |
const wantedSans = localFont({
|
| 34 | 33 |
src: [ |
| 35 | 34 |
{
|
... | ... | @@ -96,6 +95,17 @@ |
| 96 | 95 |
> |
| 97 | 96 |
<head> |
| 98 | 97 |
<script dangerouslySetInnerHTML={{ __html: THEME_BOOTSTRAP_SCRIPT }} />
|
| 98 |
+ {/* @font-face는 CSS를 다 읽은 뒤에야 발견돼 본문이 한 번 폴백 서체로 그려진다.
|
|
| 99 |
+ 거의 모든 화면이 쓰는 regular만 미리 받아 그 깜빡임을 없앤다 — 600/800은 |
|
| 100 |
+ 빈도가 낮아 선점 비용이 이득보다 크다. next/font로 넣은 wantedSans는 Next.js가 |
|
| 101 |
+ 알아서 preload하므로 여기 없다. */} |
|
| 102 |
+ <link |
|
| 103 |
+ rel="preload" |
|
| 104 |
+ as="font" |
|
| 105 |
+ type="font/woff2" |
|
| 106 |
+ href="/fonts/pretendard-regular.woff2" |
|
| 107 |
+ crossOrigin="anonymous" |
|
| 108 |
+ /> |
|
| 99 | 109 |
</head> |
| 100 | 110 |
<body> |
| 101 | 111 |
<FeedbackProvider> |
--- next.config.ts
+++ next.config.ts
... | ... | @@ -31,9 +31,20 @@ |
| 31 | 31 |
async headers() {
|
| 32 | 32 |
return [ |
| 33 | 33 |
{
|
| 34 |
- source: "/((?!_next/static|_next/image).*)", |
|
| 34 |
+ source: "/((?!_next/static|_next/image|fonts/).*)", |
|
| 35 | 35 |
headers: SECURITY_HEADERS, |
| 36 | 36 |
}, |
| 37 |
+ {
|
|
| 38 |
+ // `public/fonts`는 Next.js가 해시 경로를 붙여주지 않아 위 no-store 규칙에 그대로 |
|
| 39 |
+ // 걸린다 — 제외하지 않으면 페이지를 열 때마다 Pretendard 2.2MB를 다시 받는다. |
|
| 40 |
+ // `immutable`은 일부러 뺐다: 파일명에 버전이 없어서 폰트를 같은 이름으로 교체했을 때 |
|
| 41 |
+ // 새로고침조차 먹지 않게 된다. |
|
| 42 |
+ source: "/fonts/:path*", |
|
| 43 |
+ headers: [ |
|
| 44 |
+ { key: "Cache-Control", value: "public, max-age=31536000" },
|
|
| 45 |
+ { key: "X-Content-Type-Options", value: "nosniff" },
|
|
| 46 |
+ ], |
|
| 47 |
+ }, |
|
| 37 | 48 |
]; |
| 38 | 49 |
}, |
| 39 | 50 |
}; |
--- proxy.ts
+++ proxy.ts
... | ... | @@ -49,8 +49,14 @@ |
| 49 | 49 |
return NextResponse.next(); |
| 50 | 50 |
} |
| 51 | 51 |
|
| 52 |
+/** |
|
| 53 |
+ * 정적 자산은 인증 대상이 아니다. 폰트 확장자가 목록에 있어야 하는 이유: `public/fonts`의 |
|
| 54 |
+ * Pretendard는 `next/font`를 거치지 않아 `/_next/static`이 아닌 `/fonts/*`로 서빙된다 — |
|
| 55 |
+ * 빼면 세션 없는 방문자의 폰트 요청이 `/login`으로 리다이렉트돼 로그인 화면만 대체 서체로 |
|
| 56 |
+ * 그려진다. |
|
| 57 |
+ */ |
|
| 52 | 58 |
export const config = {
|
| 53 | 59 |
matcher: [ |
| 54 |
- '/((?!_next/static|_next/image|favicon\\.ico|robots\\.txt|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)', |
|
| 60 |
+ '/((?!_next/static|_next/image|favicon\\.ico|robots\\.txt|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|woff2|woff|ttf|otf)$).*)', |
|
| 55 | 61 |
], |
| 56 | 62 |
}; |
+++ public/fonts/pretendard-OFL.txt
... | ... | @@ -0,0 +1,94 @@ |
| 1 | +Copyright (c) 2021, Kil Hyung-jin (https://github.com/orioncactus/pretendard), | |
| 2 | +with Reserved Font Name Pretendard. | |
| 3 | + | |
| 4 | +This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| 5 | +This license is copied below, and is also available with a FAQ at: | |
| 6 | +https://scripts.sil.org/OFL | |
| 7 | + | |
| 8 | + | |
| 9 | +----------------------------------------------------------- | |
| 10 | +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| 11 | +----------------------------------------------------------- | |
| 12 | + | |
| 13 | +PREAMBLE | |
| 14 | +The goals of the Open Font License (OFL) are to stimulate worldwide | |
| 15 | +development of collaborative font projects, to support the font creation | |
| 16 | +efforts of academic and linguistic communities, and to provide a free and | |
| 17 | +open framework in which fonts may be shared and improved in partnership | |
| 18 | +with others. | |
| 19 | + | |
| 20 | +The OFL allows the licensed fonts to be used, studied, modified and | |
| 21 | +redistributed freely as long as they are not sold by themselves. The | |
| 22 | +fonts, including any derivative works, can be bundled, embedded, | |
| 23 | +redistributed and/or sold with any software provided that any reserved | |
| 24 | +names are not used by derivative works. The fonts and derivatives, | |
| 25 | +however, cannot be released under any other type of license. The | |
| 26 | +requirement for fonts to remain under this license does not apply | |
| 27 | +to any document created using the fonts or their derivatives. | |
| 28 | + | |
| 29 | +DEFINITIONS | |
| 30 | +"Font Software" refers to the set of files released by the Copyright | |
| 31 | +Holder(s) under this license and clearly marked as such. This may | |
| 32 | +include source files, build scripts and documentation. | |
| 33 | + | |
| 34 | +"Reserved Font Name" refers to any names specified as such after the | |
| 35 | +copyright statement(s). | |
| 36 | + | |
| 37 | +"Original Version" refers to the collection of Font Software components as | |
| 38 | +distributed by the Copyright Holder(s). | |
| 39 | + | |
| 40 | +"Modified Version" refers to any derivative made by adding to, deleting, | |
| 41 | +or substituting -- in part or in whole -- any of the components of the | |
| 42 | +Original Version, by changing formats or by porting the Font Software to a | |
| 43 | +new environment. | |
| 44 | + | |
| 45 | +"Author" refers to any designer, engineer, programmer, technical | |
| 46 | +writer or other person who contributed to the Font Software. | |
| 47 | + | |
| 48 | +PERMISSION & CONDITIONS | |
| 49 | +Permission is hereby granted, free of charge, to any person obtaining | |
| 50 | +a copy of the Font Software, to use, study, copy, merge, embed, modify, | |
| 51 | +redistribute, and sell modified and unmodified copies of the Font | |
| 52 | +Software, subject to the following conditions: | |
| 53 | + | |
| 54 | +1) Neither the Font Software nor any of its individual components, | |
| 55 | +in Original or Modified Versions, may be sold by itself. | |
| 56 | + | |
| 57 | +2) Original or Modified Versions of the Font Software may be bundled, | |
| 58 | +redistributed and/or sold with any software, provided that each copy | |
| 59 | +contains the above copyright notice and this license. These can be | |
| 60 | +included either as stand-alone text files, human-readable headers or | |
| 61 | +in the appropriate machine-readable metadata fields within text or | |
| 62 | +binary files as long as those fields can be easily viewed by the user. | |
| 63 | + | |
| 64 | +3) No Modified Version of the Font Software may use the Reserved Font | |
| 65 | +Name(s) unless explicit written permission is granted by the corresponding | |
| 66 | +Copyright Holder. This restriction only applies to the primary font name as | |
| 67 | +presented to the users. | |
| 68 | + | |
| 69 | +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
| 70 | +Software shall not be used to promote, endorse or advertise any | |
| 71 | +Modified Version, except to acknowledge the contribution(s) of the | |
| 72 | +Copyright Holder(s) and the Author(s) or with their explicit written | |
| 73 | +permission. | |
| 74 | + | |
| 75 | +5) The Font Software, modified or unmodified, in part or in whole, | |
| 76 | +must be distributed entirely under this license, and must not be | |
| 77 | +distributed under any other license. The requirement for fonts to | |
| 78 | +remain under this license does not apply to any document created | |
| 79 | +using the Font Software. | |
| 80 | + | |
| 81 | +TERMINATION | |
| 82 | +This license becomes null and void if any of the above conditions are | |
| 83 | +not met. | |
| 84 | + | |
| 85 | +DISCLAIMER | |
| 86 | +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 87 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
| 88 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
| 89 | +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
| 90 | +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 91 | +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
| 92 | +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 93 | +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
| 94 | +OTHER DEALINGS IN THE FONT SOFTWARE. |
+++ public/fonts/pretendard-extrabold.woff2
| Binary file is not shown |
+++ public/fonts/pretendard-regular.woff2
| Binary file is not shown |
+++ public/fonts/pretendard-semibold.woff2
| Binary file is not shown |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?