-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Storybook 세팅을 하면서 겪었던 문제에 대해 정리한다.
- 패키지 버전: Next.js 16, React 19, Storybook 10
next local font 네트워크 요청 404 이슈
next local font를 사용하여 폰트를 설정하는 과정에서 storybook에서 font 네트워크 요청에 실패하는 문제가 발생했다.
네트워크 탭을 살펴보면 http://localhost:6006/src/assets/fonts/PretendardVariable.woff2 를 요청하고 있다.
프로젝트에 이미 src/assets/fonts 폴더에 정상적으로 폰트가 저장되어있는데, 왜 이런 현상이 생기는지 이해할 수 없었다.
답은 공식문서에서 찾을 수 있었다.
https://storybook.js.org/tutorials/intro-to-storybook/react/en/deploy/
storybook-static 디렉토리가 storybook의 root 디렉토리가 된다는 것이다.
그렇다면 storybook-static/src/assets/fonts 디렉토리에서 폰트 파일을 요청하고 있다는 말인데, 해당 디렉토리에 폰트가 있는지 알아보기 위해 storybook build를 진행해보았다.
빌드된 파일들을 살펴보면 src/assets 폴더가 없는것을 볼 수 있다.
이를 해결할 수 있는 방법이 공식문서에 기재되어있다.
https://storybook.js.org/docs/get-started/frameworks/nextjs?renderer=react#nextfontlocal
// Replace your-framework with nextjs or nextjs-vite
import type { StorybookConfig } from '@storybook/your-framework';
const config: StorybookConfig = {
// ...
staticDirs: [
{
from: '../src/components/fonts',
to: 'src/components/fonts',
},
],
};
export default config;staticDirs 속성을 이용해 정적자원을 빌드파일에 추가해주어야 한다. 내 경우는 src/assets에 정적파일들을 저장하고 있으니 storybook-static/src/assets 디렉토리가 만들어지도록 다음과 같이 세팅해보았다.
staticDirs: [
'../public',
{
from: '../src/assets',
to: '/src/assets',
},
],빌드 후 확인해보면 storybook-static/src/assets/fonts 디렉토리가 정상적으로 생성된 것을 볼 수 있다.
네트워크 요청도 문제가 없는 것을 볼 수 있다.
next local font 미적용 이슈
export const primary = localFont({
src: '../assets/fonts/PretendardVariable.woff2',
variable: '--font-primary',
display: 'swap',
});메인 프로젝트와 같은 프레임워크 환경을 구성하기 위해 storybook의 프레임워크를 nextjs로 설정해주었다.
import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: ['@storybook/addon-docs', '@storybook/addon-a11y'],
framework: {
name: '@storybook/nextjs',
options: {},
},
...
}그리고 font가 storybook에 적용될 수 있도록 preview.tsx의 decorator 기능을 이용하여 font를 설정해주었다.
방법 1
const preview: Preview = {
...
decorators: [
(Story) => (
<div className={`${primary.className}`}>
<Story />
</div>
),
},
],
};방법 2
const preview: Preview = {
...
decorators: [
(Story) => (
<div className={`${primary.variable}`}>
<div className='font-primary'>
<Story />
</div>
</div>
),
},
],
};이렇게 지정해주면 기본 컴포넌트들은 폰트가 잘 설정이 되는데, 모달 같이 별도의 portal에서 생성되는 컴포넌트들은 font가 적용되지 않는 문제가 발생했다.
- Button(기본 컴포넌트)
- Modal
Next.js 프로젝트에서 RootLayout에서 아래 와 같이 className에 font.className과 font.variable을 추가해주면 정상적으로 폰트가 적용이 잘 되었기에 font를 처리하는 환경이 다른점이 있는지 확인해 볼 필요가 있었다.
Storybook 공식문서
Storybook에서 next local font의 기능을 일부 지원하지 않는다고 명시되어있다.
이 중 첫 번째 항목을 보면, 글꼴 로더 구성을 지원하지 않는다 라고 적혀있다. 그래서 next.js 프로젝트와 Storybook nextjs 환경에서 폰트 처리 결과를 확인해보았다. (사실 이부분의 문제는 아니었다.)
Storybook
Storybook에서 Button 컴포넌트의 정보를 확인해보면 아래와 같이 보인다.
<div class="__variable_font-e25342">
<div class="font-primary">
<div>
<button class="font-primary rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
Open Modal
</button>
</div>
</div>
</div>
Storybook의 내부에 <style>을 통해 font 정보가 선언되고 있고, 이 font 정보를 이용해 classname을 잘 생성하고 있다.
그리고 preview decorator에서 컴포넌트를 감싼 div에서 'font-primary' 를 지정해주고 있기 때문에, 컴포넌트 내부에서 폰트를 상속 받아 'font-primary'가 잘 적용되고 있었다.
하지만 Modal에서는 컴포넌트에 직접 'font-primary' 클래스를 추가해줘도 폰트를 인식을 하지 못하고 있었다.
// modal
<div class="font-primary fixed inset-0 z-50 flex items-center justify-center"> // 여기서 font-primary를 지정해줌
...
</div>
그렇다면 next.js 프로젝트에서는 왜 정상적으로 동작하는지 확인해보았다.
<body class="primary_670b572d-module__0jWoVq__variable font-primary antialiased">
primary_670b572d-module__0jWoVq__variable 라는 css module을 생성하고 그 안에서 --font-primary라는 변수를 만들어주고 body에 주입해주고 있다.
그래서 그 하위에 있는 모든 요소들은 --font-primary 변수에 접근할 수 있어서 폰트가 정상적으로 적용되고 있었다.
이를 통해 중요한 문제점을 알 수 있었다.
next.js 프로젝트에서는 body에 --font-primary 변수를 주입하고 있기 때문에 하위 요소 전체에서 --font-primary에 접근할 수 있지만,
Storybook decorator에 --font-primary 변수를 주입해주게 되면 decorator 하위 요소에서만 --font-primary에 접근할 수 있다. 그러니까 portal을 사용해 별도의 공간에 렌더링되는 요소에서는 --font-primary에 접근하지 못하는 것이다.
정리해보면 아래와 같이 표현 가능하다.
next.js 프로젝트
<body class="primary_670b572d-module__0jWoVq__variable font-primary antialiased"> // body에서 font 스타일 생성
<button class="font-primary">버튼</button> // font-primary 사용 가능
<portal>
<Modal class="font-primary"></Modal> // font-primary 사용 가능
</portal>
</body>storybook
<body>
<decorator class="primary_670b572d-module__0jWoVq__variable font-primary antialiased"> // body가 아닌 decorator에서 font 스타일 생성
<button>버튼</button> // font-primary 사용 가능
</decorator>
<div> // 새로운 portal에서 Modal 생성
<Modal class="font-primary">모달</Modal> // font-primary 사용 불가!!
</div>
</body>이 문제를 해결하려면 next.js 프로젝트와 동일하게 body에 --font-primary가 주입되는 구조로 바꿔야한다.
decorator가 실행될 때 body에 폰트 적용
decorator가 실행될 때 useEffect를 이용해 body에 className을 추가하는 방법을 시도해보았다.
decorators: [
(Story) => {
useEffect(() => {
console.log('body className added');
document.body.classList.add(primary.variable);
// cleanup: 언마운트시 제거
return () => {
document.body.classList.remove(primary.variable);
};
}, []);
if (typeof document !== 'undefined') {
document.body.classList.add(primary.variable);
}
return <Story />;
},
]정상적으로 작동은 됐지만, storybook의 각 요소가 렌더링 될 때마다 useEffect가 실행되어 메모리 낭비의 이유로 이 방법은 차용하지 않았다.
previewHead 속성 이용
https://storybook.js.org/docs/api/main-config/main-config-preview-head
위 글을 보면
Programmatically adjust the preview of your Storybook. Most often used by addon authors.
라고 명시되어있다. 한번만 head에 스타일을 지정해주면 불필요한 메모리 낭비를 해결할 수 있을 것이라고 생각해 이 방법을 시도해보았다.
const config: StorybookConfig = {
...
previewHead: (head) => `
${head}
<script>
(function() {
function addFontClass() {
document.documentElement.classList.add('${primary.variable}');
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addFontClass, { once: true });
} else {
addFontClass();
}
})();
</script>
`,
};하지만 npm run storybook 을 실행해보니 오류가 발생했다.
One or more extensionless imports detected: "../src/app/font" in file "C:\Users\cyga2\OneDrive\바탕 화면\git\create-next-app\my-app\.storybook\main.ts".
For maximum compatibility, you should add an explicit file extension to this import.
Storybook will attempt to resolve it automatically, but this may change in the future.
If adding the extension results in an error from TypeScript, we recommend setting moduleResolution to "bundler" in tsconfig.json
or alternatively look into the allowImportingTsExtensions option.
SB_CORE-SERVER_0007 (MainFileEvaluationError): Storybook couldn't evaluate your .storybook\main.ts file.
Original error:
Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '\node_modules\next\font\local' is not supported resolving ES modules imported from \src\app\font.ts
Did you mean to import "next/font/local/index.js"?
at finalizeResolution (node:internal/modules/esm/resolve:263:11)
at moduleResolve (node:internal/modules/esm/resolve:860:10)
at defaultResolve (node:internal/modules/esm/resolve:984:11)
at nextResolve (node:internal/modules/esm/hooks:748:28)
at Hooks.resolve (node:internal/modules/esm/hooks:240:30)
at MessagePort.handleMessage (node:internal/modules/esm/worker:199:24)
at [nodejs.internal.kHybridDispatch] (node:internal/event_target:827:20)
at MessagePort.<anonymous> (node:internal/per_context/messageport:23:28)
at loadMainConfig (file:///C:/Users/cyga2/OneDrive/%EB%B0%94%ED%83%95%20%ED%99%94%EB%A9%B4/git/create-next-app/my-app/node_modules/storybook/dist/_node-chunks/chunk-IH6W7E4F.js:12257:11)
at async buildDevStandalone (file:///C:/Users/cyga2/OneDrive/%EB%B0%94%ED%83%95%20%ED%99%94%EB%A9%B4/git/create-next-app/my-app/node_modules/storybook/dist/core-server/index.js:12142:18)
at async withTelemetry (file:///C:/Users/cyga2/OneDrive/%EB%B0%94%ED%83%95%20%ED%99%94%EB%A9%B4/git/create-next-app/my-app/node_modules/storybook/dist/_node-chunks/chunk-BJYCX337.js:278:12)
at async dev (file:///C:/Users/cyga2/OneDrive/%EB%B0%94%ED%83%95%20%ED%99%94%EB%A9%B4/git/create-next-app/my-app/node_modules/storybook/dist/bin/core.js:3668:3)
at async _Command.<anonymous> (file:///C:/Users/cyga2/OneDrive/%EB%B0%94%ED%83%95%20%ED%99%94%EB%A9%B4/git/create-next-app/my-app/node_modules/storybook/dist/bin/core.js:3735:3)
Broken build, fix the error above.
You may need to refresh the browser.
Failed to load preset: "C:/Users/cyga2/OneDrive/바탕 화면/git/create-next-app/my-app/.storybook/main.ts"
Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '.\node_modules\next\font\local' is not supported resolving ES modules imported from .\src\app\font.ts
Did you mean to import "next/font/local/index.js"?
at finalizeResolution (node:internal/modules/esm/resolve:263:11)
at moduleResolve (node:internal/modules/esm/resolve:860:10)
at defaultResolve (node:internal/modules/esm/resolve:984:11)
at nextResolve (node:internal/modules/esm/hooks:748:28)
at Hooks.resolve (node:internal/modules/esm/hooks:240:30)
at MessagePort.handleMessage (node:internal/modules/esm/worker:199:24)
at [nodejs.internal.kHybridDispatch] (node:internal/event_target:827:20)
at MessagePort.<anonymous> (node:internal/per_context/messageport:23:28)간단하게 정리해보면 아래와 같다.
- preview.tsx: Webpack 로더가 번들링 → next/font/local 처리 가능 ✅
- main.ts: 순수 Node.js ESM 직접 실행 → next/font/local 처리 불가 ❌
따라서 main.ts에서 폰트를 처리할 방법이 없기 때문에 이 방법을 사용할 수 없었다.
Storybook addon 사용
https://storybook.js.org/addons/storybook-addon-root-attributes
Storybook addon 중 storybook-addon-root-attributes 라는 애드온이 있어 이것을 사용해보기로 했다.
이 애드온은 또는 에 직접 속성을 설정할 수 있다고 설명되어있다.
storybook 7에서 동작한다고 설명되어있긴 했는데, 혹시 몰라서 시도해보았다.
PS C:\Users\cyga2\OneDrive\바탕 화면\git\create-next-app\my-app> npm i -D storybook-addon-root-attributes
npm error code ERESOLVE
npm error ERESOLVE could not resolve
npm error
npm error While resolving: storybook-addon-root-attributes@2.1.3
npm error Found: react@19.2.0
npm error node_modules/react
npm error peer react@">=16" from @mdx-js/react@3.1.1
npm error node_modules/@mdx-js/react
npm error @mdx-js/react@"^3.0.0" from @storybook/addon-docs@10.0.5
npm error node_modules/@storybook/addon-docs
npm error dev @storybook/addon-docs@"^10.0.5" from the root project
npm error react@"^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" from @storybook/addon-docs@10.0.5
npm error node_modules/@storybook/addon-docs
npm error dev @storybook/addon-docs@"^10.0.5" from the root project
npm error 12 more (@storybook/icons, @storybook/nextjs, ...)
npm error
npm error Could not resolve dependency:
npm error peerOptional react@"^17.0.0 || ^18.0.0" from storybook-addon-root-attributes@2.1.3
npm error node_modules/storybook-addon-root-attributes
npm error dev storybook-addon-root-attributes@"*" from the root project
npm error
npm error Conflicting peer dependency: react@18.3.1
npm error node_modules/react
npm error peerOptional react@"^17.0.0 || ^18.0.0" from storybook-addon-root-attributes@2.1.3
npm error node_modules/storybook-addon-root-attributes
npm error dev storybook-addon-root-attributes@"*" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error C:\Users\cyga2\AppData\Local\npm-cache\_logs\2025-11-09T05_29_51_717Z-eresolve-report.txt애드온을 설치하니 react 17 또는 18 만 peerDeps로 되어있어서 설치가 되지 않았다. (나는 React 19 환경에서 사용했다.)
일단 동작이 되는지 확인하기 위해 --legacy-peer-deps 키워드로 강제 설치를 진행해보았다.
npm i -D storybook-addon-root-attributes --legacy-peer-deps
이후 preview.tsx에서 html에 primary.variable이 적용되도록 설정 했다.
const preview: Preview = {
parameters: {
rootAttribute: {
root: 'html',
attribute: 'class',
defaultState: {
name: 'Default Font',
value: primary.variable,
},
},
},
}storybook을 실행해보았는데, 아래와 같은 오류가 발생했다.
X [ERROR] Could not resolve "@storybook/manager-api"
node_modules/storybook-addon-root-attributes/dist/manager.mjs:1:59:
1 │ import { addons, types, useStorybookApi, useGlobals } from '@storybook/manager-api';
╵ ~~~~~~~~~~~~~~~~~~~~~~~~
You can mark the path "@storybook/manager-api" as external to exclude it from the bundle, which
will remove this error and leave the unresolved path in the bundle.
X [ERROR] Could not resolve "@storybook/components"
node_modules/storybook-addon-root-attributes/dist/manager.mjs:2:97:
2 │ import { AddonPanel, Div, H2, Button, WithTooltip, H4, TooltipLinkList, IconButton, Icons } from '@storybook/components';
╵ ~~~~~~~~~~~~~~~~~~~~~~~
You can mark the path "@storybook/components" as external to exclude it from the bundle, which
will remove this error and leave the unresolved path in the bundle.
Error: Build failed with 2 errors:
node_modules/storybook-addon-root-attributes/dist/manager.mjs:1:59: ERROR: Could not resolve "@storybook/manager-api"
node_modules/storybook-addon-root-attributes/dist/manager.mjs:2:97: ERROR: Could not resolve "@storybook/components"
at failureErrorWithLog (.\node_modules\esbuild\lib\main.js:1467:15)
at .\node_modules\esbuild\lib\main.js:926:25
at runOnEndCallbacks (.\node_modules\esbuild\lib\main.js:1307:45)
at buildResponseToResult (.\node_modules\esbuild\lib\main.js:924:7)
at .\node_modules\esbuild\lib\main.js:951:16
at responseCallbacks.<computed> (.\node_modules\esbuild\lib\main.js:603:9)
at handleIncomingPacket (.\node_modules\esbuild\lib\main.js:658:12)
at Socket.readFromStdout (.\node_modules\esbuild\lib\main.js:581:7)
at Socket.emit (node:events:518:28)
at addChunk (node:internal/streams/readable:561:12)
Broken build, fix the error above.
You may need to refresh the browser.storybook/manager-api, @storybook/components 를 인식하지 못하고 있어서 설치를 해봤는데, 또 아래와 같은 오류가 발생했다.
PS C:\Users\cyga2\OneDrive\바탕 화면\git\create-next-app\my-app> npm i @storybook/manager-api
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: my-app@0.1.0
npm error Found: storybook@10.0.6
npm error node_modules/storybook
npm error dev storybook@"^10.0.5" from the root project
npm error
npm error Could not resolve dependency:
npm error peer storybook@"^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" from @storybook/manager-api@8.6.14
npm error node_modules/@storybook/manager-api
npm error @storybook/manager-api@"*" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error C:\Users\cyga2\AppData\Local\npm-cache\_logs\2025-11-09T07_35_26_240Z-eresolve-report.txt
npm error A complete log of this run can be found in: C:\Users\cyga2\AppData\Local\npm-cache\_logs\2025-11-09T07_35_26_240Z-debug-0.log
PS C:\Users\cyga2\OneDrive\바탕 화면\git\create-next-app\my-app> peerDeps가 storybook 8버전까지만 명시되어있었고, 유지보수가 더 이상 진행되지 않는 패키지라고 판단해 애드온을 사용하는 방식은 사용을 포기했다.
preview.tsx가 로딩될 때 한번만 실행
decorator는 컴포넌트가 렌더링 될 때 마다 실행되지만 preview.tsx 파일 자체는 한번만 실행되기 때문에 그 타이밍에 body에 className을 추가하는 구문을 작성해주었다.
(() => {
console.log('body className added');
const addFontClass = () => {
document.documentElement.classList.add(primary.variable);
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addFontClass, { once: true });
} else {
addFontClass();
}
})();
함수 중복 실행도 없고 정상적으로 적용이 되어 이대로 마무리 하긴 했는데, 이 방법이 최선인지는 잘 모르겠다. 다른 방법들을 더 확인해보아야 할 듯 하다.
