๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • What would life be If we had no courage to attemp anything?
Development/Next.js

[Next.js] tailwind CSS ๋กœ์ปฌ ํฐํŠธ ์ ์šฉ (feat. Type '{ children: Element[]; className: string; }' is not assignable to type)

by DevIseo 2025. 3. 14.

 

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

 

  1. Pretendard ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œ ํ•ฉ๋‹ˆ๋‹ค.
    • https://cactus.tistory.com/306
    • ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ํฐํŠธ ๊ฒฝ๋Ÿ‰ํ™”๋ฅผ ์œ„ํ•ด .woff2 ํ™•์žฅ์ž ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œ ํ–ˆ์Šต๋‹ˆ๋‹ค.
  2. ๋‹ค์Œ ๊ฒฝ๋กœ์— ๊ธ€๊ผด์„ ์œ„์น˜ ์‹œํ‚ต๋‹ˆ๋‹ค.
    • ROOT/public/fonts/PretendardVariable.woff2
  3. 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๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

tw?: string; ์†์„ฑ์ด ์ถ”๊ฐ€๋˜์—ˆ์ง€๋งŒ, ๊ธฐ์กด className?: string;์ด ๋Œ€์ฒด ๋˜์—ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ์ƒ๊ฒผ๋‹ค.

 

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๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ผ๋‹จ์€ ๋งˆ๋ฌด๋ฆฌ ํ•˜์˜€๋‹ค.

๋Œ“๊ธ€