next.js + tailwind CSSํ๋ก์ ํธ์์ ๊ธ์จ์ฒด๋ฅผ ์ ์ฉ ํ๋ ๋ฐฉ๋ฒ
ํด๋น ๊ฒ์๊ธ์ ๋ค์ ๊ธฐ์ ์คํ์ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค.
- next.js v15 pages-router
- react v19
- typescript
- tailwindCSS
- shadcnUI
ํ๋ก์ ํธ์์ ์ฌ์ฉํ๋ pretendard๋ฅผ ์์๋ก ์์ฑํฉ๋๋ค.
Optimizing: Fonts | Next.js
Optimize your application's web fonts with the built-in `next/font` loaders.
nextjs.org
- Pretendard ํ์ผ์ ๋ค์ด๋ก๋ ํฉ๋๋ค.
- https://cactus.tistory.com/306
- ํด๋น ํ๋ก์ ํธ์์๋ ํฐํธ ๊ฒฝ๋ํ๋ฅผ ์ํด .woff2 ํ์ฅ์ ํ์ผ์ ๋ค์ด๋ก๋ ํ์ต๋๋ค.
- ๋ค์ ๊ฒฝ๋ก์ ๊ธ๊ผด์ ์์น ์ํต๋๋ค.
- ROOT/public/fonts/PretendardVariable.woff2
- pages/_app.tsx ์ pretendard ๊ธ๊ผด์ ์ ์ํฉ๋๋ค.
//pages/_app.tsx
import '@/styles/globals.css';
import type { AppProps } from 'next/app';
// next.js์ font-local ์ฌ์ฉ
**import localFont from 'next/font/local';**
**export const pretendard = localFont({
src: '../public/fonts/PretendardVariable.woff2', // ๊ฒฝ๋ก ์ฃผ์!!
display: 'swap',
weight: '45 920',
variable: '--font-pretendard',
});**
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
4. styles/global.css์์ body ๊ฐ์ font-family๋ฅผ ์ง์ ํฉ๋๋ค.
//styles/global.css
body {
font-family: var(--font-pretendard); //localFont๋ก ๋ถ๋ฌ์ฌ ๋ ์ง์ ํ variable ๊ฐ ์
๋ ฅ
}
5. tailwind.config.ts์ fontFamily ์์ฑ์ ์ถ๊ฐํฉ๋๋ค.
//tailwind.config.ts
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./app/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
fontFamily: {
pretendard: ['var(--font-pretendard)'], //localFont๋ก ๋ถ๋ฌ์ฌ ๋ ์ง์ ํ variable ๊ฐ ์
๋ ฅ
},
},
},
}
6. _document.tsx์์ localFont๋ก ๋ถ๋ฌ์จ pretendard๋ฅผ className์ ํตํด ์ ์ฉํฉ๋๋ค.
//pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document';
import { pretendard } from './_app';
export default function Document() {
return (
<Html lang="en">
<Head />
<body className={pretendard.variable}>
<Main />
<NextScript />
</body>
</Html>
);
}
className์ ํตํด pretendard๋ฅผ ์ง์ ํ ๋, Html ํ๊ทธ์ body ํ๊ทธ ๋ ์ค ํ๋๋ฅผ ์ ํํ์ฌ ์ง์
- Html ํ๊ทธ์ body ํ๊ทธ ์ง์ ์ด๋ค ์ฐจ์ด๊ฐ ์๋?
์ ์ฉ ์์น ์ ์ฉ ๋ฒ์ ์คํ์ผ ์์ ์ถ์ฒํ๋ ๊ฒฝ์ฐ <html className="pretendard"> ์ ์ฒด ๋ฌธ์ ๋ชจ๋ ์์๊ฐ ํฐํธ ์์ โ ๋ชจ๋ ํ ์คํธ์ ๊ฐ์ ํฐํธ๋ฅผ ์ ์ฉํ๋ ค๋ฉด <html>์ ์ ์ฉ <body className="pretendard"> body ๋ด๋ถ ์์๋ง html ์คํ์ผ์ ์ํฅ์ ๋ฐ์ ์ ์์ โ ํน์ ์คํ์ผ์ body๋ง ์ ์ฉํ๋ ค๋ฉด <body>์ ์ ์ฉ
๐จ Trouble Shooting
Type '{ children: Element[]; className: string; }' is not assignable to type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'. Property 'className' does not exist on type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'.
ํด๋น ํ๋ก์ ํธ์ ํ์ด์ง ์ต์์์ธ document ํ์ผ์ ํ์ฅ์๋ tsx ๋ก className์ ์ ์ฉ์ ํด๋น ๋ฌธ๊ตฌ๋ก ๋น๋๊ฐ ๋ถ๊ฐ ํ๋ค.
๊ธฐ์กด ์ฌ๋ด ์๋น์ค์ ๊ฒฝ์ฐ javascript ๊ธฐ๋ฐ์ ํ๋ก์ ํธ๋ผ localFont๋ฅผ className์ผ๋ก ์ง์ ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์์์ผ๋, ์ด๋ฒ ํ๋ก์ ํธ๋ typescipt๋ก ๊ฐ๋ฐํ๋ฉด์ ํด๋น ์ค๋ฅ๋ฅผ ๋ง๋๊ฒ ๋์๋ค. ๊ฒ์ ์ ๋์ค๋ ๋๋ถ๋ถ์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ app router๋ฅผ ํ์ฉํ next.js์์์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด์๋ค. ํ์ง๋ง ์๋ ๋งํฌ์ ๊ธ์ ์ฐธ๊ณ ํ์ฌ ์์ ํ ์ ์์๋ค.
How to Use class instead of className with Preact and TypeScript
Bottom Line Up Front If you are using TypeScript with Preact aliased as React, you can add...
dev.to
โจ์ค๋ฅ ํด๊ฒฐ
ํ์ ์ ์ธ์ ํตํด ํด๊ฒฐ
์ค๋ฅ ๋ฉ์์ง์์๋ ํ์ธํ ์ ์๋ฏ์ด HTMLAttributes์ className ์์ฑ์ด ์์ด ๋ฐ์ํ ํ์ ์ค๋ฅ์ด๋ค.
๋ฐ๋ผ์, ์์์ ์ธ๊ธํ ๋งํฌ์์ ๋ต์ ์ป์ด ๋ค์๊ณผ ๊ฐ์ด ์งํํ์๋ค.
1. root ๊ฒฝ๋ก์ ํ์ ์ ์ธ ํ์ผ ์ถ๊ฐ ( ์ด๋ฆ.d.ts )
- ํ์ผ์ ์ฉ๋๋ฅผ ์ง๊ด์ ์ผ๋ก ํ๊ธฐ ์ํด className.d.ts ๋ก ๋ช ๋ช ํ๋ค.
2. ํ์ ์ ์ธ
/**
* declare - TypeScript์๊ฒ ํ์
์ ๋ณด๋ง ์ ๊ณตํ๋ ์ ์ธ์์ ์๋ฆผ
* namespace - ํน์ ํ์
๋ค์ ๊ทธ๋ฃนํํ์ฌ ๋ค์์คํ์ด์ค๋ก ๊ด๋ฆฌ
* interface - ๊ฐ์ฒด์ ๊ตฌ์กฐ๋ฅผ ์ ์ํ๊ณ ํ์ฅ ๊ฐ๋ฅํ๊ฒ ๋ง๋ฆ
*/
declare namespace React {
interface HTMLAttributes {
className?: string;
}
}
๐ฅฒ๋ค์ฌ๋ค ๋ณด๊ธฐ..
ํ์ง๋ง HTMLAttributes์ className์ด ์ค์ ๋ก ์๋๊ฑด์ง ๊ถ๊ธํ๋ค.
//_app.tsx
type TestProps = React.HTMLAttributes<HTMLDivElement>;
const testProps: TestProps = {
className: 'test-class',
};
console.log(testProps); // { className: 'test-class' }
ํด๋น ๋ก๊ทธ์์๋ className์ด ์ฐํ๋ค.
className ์ด HTMLAttributes<T> ๋ด๋ถ์ ํฌํจ๋์ด ์๋์ง ๊ฒ์ํ์๊ณ ์กด์ฌํ๊ณ ์์์ ํ์ธํ๋ค.
์ถ๊ฐ์ ์ผ๋ก, ํด๋น ๊ฐ์ด HTMLAttributes์ ์กด์ฌํ๋ ๊ฒ์ธ์ง ํ์ธํ์๋ค.
//pages/test.tsx
import { cn } from '@/lib/utils';
const MyComponent = ({ className }: React.HTMLAttributes<HTMLDivElement>) => {
console.log('className:', className); // undefined
return <div className={cn('base-class', className)}>Hello</div>;
};
export default MyComponent;
ํ์ง๋ง ํด๋น ๋ก๊ทธ์์ undefined๋ก ๋ณ๊ฒฝ๋์๋ค.
shadcnUI๋ฅผ ์ค์นํ๋ฉด์ ์ถ๊ฐํ๋ utils์ ์ฝ๋์์ react ๋ด๋ถ์์ HTMLAttributes<T>๊ฐ ์ฌ์ ์ ๋ ๊ฒ์ผ๋ก ๋ณด์ธ๋ค.
Manual Installation
Add dependencies to your project manually.
ui.shadcn.com
// @/lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
ํด๋น ํ์ผ์ ์ญํ ์ ์๋ ๋ธ๋ก๊ทธ์ ์ ์ ๋ฆฌ๋์ด ์์๋ค.
clsx, twMerge, cn, shadcn-ui
shadcn-ui ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฃผ ๋ง๋๊ณ , ํ์ ์์ ์ค ํ๋์ธ cn ํจ์๋ฅผ ์์ธํ ๋ค์ด๋ค ๋ณด๋ ค๊ณ ํฉ๋๋ค. ๊ทธ๋ฌ๋ฉด ์ดํด๋๊ฐ ๋์์ง๊ณ ๊ธฐ์ต์๋ ๋์์ด ๋ฉ๋๋ค. shadcn-ui@latest init ์ผ๋ก ์ค์นํ๋ฉด, l
polyframe.tistory.com
shadcn UI๋ฅผ ์ฌ์ฉํ๋ฉด์ tailwind CSS์ className์ ๋ณํฉ์์ผ์ฃผ๊ธฐ ์ํ ์ฝ๋๋ก ์์ฝ๋๋๋ฐ, shadcnUI์ install Manual์ ์ ํ ์ถ๊ฐ๋๋ ํ์ผ์ด๋ผ ํด๋น ํ์ผ์ ๊ผญ ํ์ํ ๊ฑฐ ๊ฐ๋ค. ๋ฐ๋ผ์ ์์ ๋ฐฉ๋ฒ์ ํตํด type error๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉํฅ์ผ๋ก ์ผ๋จ์ ๋ง๋ฌด๋ฆฌ ํ์๋ค.
๋๊ธ