스타일링

Nuxt 애플리케이션을 스타일링하는 방법을 배워보세요.

Nuxt는 스타일링에 있어 매우 유연합니다. 직접 스타일을 작성하거나, 로컬 및 외부 스타일시트를 참조할 수 있습니다. CSS 전처리기, CSS 프레임워크, UI 라이브러리, Nuxt 모듈을 사용하여 애플리케이션을 스타일링할 수 있습니다.

로컬 스타일시트

로컬 스타일시트를 작성하는 경우, 자연스러운 위치는 assets/ 디렉토리입니다.

컴포넌트 내에서 가져오기

페이지, 레이아웃, 컴포넌트에서 직접 스타일시트를 가져올 수 있습니다. JavaScript import 또는 CSS @import을 사용할 수 있습니다.

pages/index.vue
<script>
// 서버 사이드 호환성을 위해 정적 import 사용
import '~/assets/css/first.css'

// 주의: 동적 import는 서버 사이드에서 호환되지 않습니다
import('~/assets/css/first.css')
</script>

<style>
@import url("~/assets/css/second.css");
</style>
스타일시트는 Nuxt가 렌더링한 HTML에 인라인됩니다.

CSS 속성

Nuxt 설정에서 css 속성을 사용할 수도 있습니다. 스타일시트의 자연스러운 위치는 assets/ 디렉토리입니다. 경로를 참조하면 Nuxt가 애플리케이션의 모든 페이지에 포함시켜줍니다.

nuxt.config.ts
export default defineNuxtConfig({
  css: ['~/assets/css/main.css']
})
스타일시트는 Nuxt가 렌더링한 HTML에 인라인되며, 전역적으로 주입되어 모든 페이지에 적용됩니다.

폰트 작업하기

로컬 폰트 파일을 ~/public/ 디렉토리에, 예를 들어 ~/public/fonts에 넣으세요. 그런 다음 스타일시트에서 url()을 사용해 참조할 수 있습니다.

assets/css/main.css
@font-face {
  font-family: 'FarAwayGalaxy';
  src: url('/fonts/FarAwayGalaxy.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

그런 다음 스타일시트, 페이지 또는 컴포넌트에서 폰트 이름으로 참조하세요:

<style>
h1 {
  font-family: 'FarAwayGalaxy', sans-serif;
}
</style>

NPM을 통해 배포되는 스타일시트

npm을 통해 배포되는 스타일시트를 참조할 수도 있습니다. 인기 있는 animate.css 라이브러리를 예로 들어보겠습니다.

npm install animate.css

그런 다음 페이지, 레이아웃, 컴포넌트에서 직접 참조할 수 있습니다:

app.vue
<script>
import 'animate.css'
</script>

<style>
@import url("animate.css");
</style>

패키지는 Nuxt 설정의 css 속성에서 문자열로도 참조할 수 있습니다.

nuxt.config.ts
export default defineNuxtConfig({
  css: ['animate.css']
})

외부 스타일시트

애플리케이션에 외부 스타일시트를 포함하려면 nuxt.config 파일의 head 섹션에 link 요소를 추가하면 됩니다. 여러 방법으로 이 결과를 얻을 수 있습니다. 로컬 스타일시트도 이 방법으로 포함할 수 있습니다.

Nuxt 설정의 app.head 속성을 사용해 head를 조작할 수 있습니다:

nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      link: [{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }]
    }
  }
})

스타일시트 동적 추가

useHead 컴포저블을 사용해 코드 내에서 head에 동적으로 값을 설정할 수 있습니다.

Read more in Docs > API > Composables > Use Head.
useHead({
  link: [{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }]
})

Nuxt는 내부적으로 unhead를 사용하며, 전체 문서를 참고할 수 있습니다.

Nitro 플러그인으로 렌더된 Head 수정하기

더 고급 제어가 필요하다면, 후크로 렌더된 html을 가로채서 head를 프로그래밍적으로 수정할 수 있습니다.

다음과 같이 ~/server/plugins/my-plugin.ts에 플러그인을 만드세요:

server/plugins/my-plugin.ts
export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook('render:html', (html) => {
    html.head.push('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">')
  })
})

외부 스타일시트는 렌더 차단 리소스입니다: 브라우저가 페이지를 렌더링하기 전에 반드시 로드 및 처리되어야 합니다. 불필요하게 큰 스타일을 포함한 웹 페이지는 렌더링이 더 오래 걸립니다. 자세한 내용은 web.dev에서 확인할 수 있습니다.

전처리기 사용하기

SCSS, Sass, Less, Stylus와 같은 전처리기를 사용하려면 먼저 설치하세요.

npm install -D sass

스타일시트를 작성할 자연스러운 위치는 assets 디렉토리입니다. 그런 다음 app.vue(또는 레이아웃 파일)에서 전처리기 문법으로 소스 파일을 import할 수 있습니다.

pages/app.vue
<style lang="scss">
@use "~/assets/scss/main.scss";
</style>

또는 Nuxt 설정의 css 속성을 사용할 수도 있습니다.

nuxt.config.ts
export default defineNuxtConfig({
  css: ['~/assets/scss/main.scss']
})
두 경우 모두, 컴파일된 스타일시트는 Nuxt가 렌더링한 HTML에 인라인됩니다.

전처리된 파일에 코드를 주입해야 한다면, 예를 들어 색상 변수가 있는 Sass partial 등, Vite의 preprocessors options를 사용할 수 있습니다.

assets 디렉토리에 partial 파일을 만드세요:

$primary: #49240F;
$secondary: #E4A79D;

그런 다음 nuxt.config에서:

export default defineNuxtConfig({
  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: '@use "~/assets/_colors.scss" as *;'
        }
      }
    }
  }
})

Nuxt는 기본적으로 Vite를 사용합니다. webpack을 사용하고 싶다면 각 전처리기 로더의 문서를 참고하세요.

전처리기 워커(실험적)

Vite는 전처리기 사용을 빠르게 해주는 실험적 옵션을 제공합니다.

nuxt.config에서 이를 활성화할 수 있습니다:


export default defineNuxtConfig({
  vite: {
    css: {
      preprocessorMaxWorkers: true // CPU 개수 - 1
    }
  }
})
이 옵션은 실험적이므로 Vite 문서를 참고하고 피드백을 제공하세요.

싱글 파일 컴포넌트(SFC) 스타일링

Vue와 SFC의 가장 큰 장점 중 하나는 스타일링을 자연스럽게 처리할 수 있다는 점입니다. 컴포넌트 파일의 style 블록에 직접 CSS 또는 전처리기 코드를 작성할 수 있으므로, CSS-in-JS 같은 것을 사용하지 않아도 훌륭한 개발 경험을 얻을 수 있습니다. 하지만 CSS-in-JS를 사용하고 싶다면 pinceau와 같은 3rd party 라이브러리와 모듈을 찾을 수 있습니다.

SFC에서 컴포넌트 스타일링에 대한 포괄적인 참고 자료는 Vue 문서를 참고하세요.

클래스 및 스타일 바인딩

Vue SFC 기능을 활용하여 class 및 style 속성으로 컴포넌트를 스타일링할 수 있습니다.

<script setup lang="ts">
const isActive = ref(true)
const hasError = ref(false)
const classObject = reactive({
  active: true,
  'text-danger': false
})
</script>

<template>
  <div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>
  <div :class="classObject"></div>
</template>

자세한 내용은 Vue 문서를 참고하세요.

v-bind로 동적 스타일

style 블록 내에서 v-bind 함수를 사용해 JavaScript 변수와 표현식을 참조할 수 있습니다. 바인딩은 동적이므로, 변수 값이 변경되면 스타일도 업데이트됩니다.

<script setup lang="ts">
const color = ref("red")
</script>

<template>
  <div class="text">hello</div>
</template>

<style>
.text {
  color: v-bind(color);
}
</style>

Scoped 스타일

scoped 속성을 사용하면 컴포넌트를 독립적으로 스타일링할 수 있습니다. 이 속성으로 선언된 스타일은 해당 컴포넌트에만 적용됩니다.

<template>
  <div class="example">hi</div>
</template>

<style scoped>
.example {
  color: red;
}
</style>

CSS 모듈

module 속성을 사용해 CSS Modules를 사용할 수 있습니다. 주입된 $style 변수로 접근합니다.

<template>
  <p :class="$style.red">This should be red</p>
</template>

<style module>
.red {
  color: red;
}
</style>

전처리기 지원

SFC style 블록은 전처리기 문법을 지원합니다. Vite는 .scss, .sass, .less, .styl, .stylus 파일을 별도 설정 없이 기본 지원합니다. 먼저 설치만 하면, lang 속성으로 SFC에서 바로 사용할 수 있습니다.

<style lang="scss">
  /* 여기에 scss를 작성하세요 */
</style>

자세한 내용은 Vite CSS 문서@vitejs/plugin-vue 문서를 참고하세요. webpack 사용자는 vue loader 문서를 참고하세요.

PostCSS 사용하기

Nuxt는 postcss가 내장되어 있습니다. nuxt.config 파일에서 설정할 수 있습니다.

nuxt.config.ts
export default defineNuxtConfig({
  postcss: {
    plugins: {
      'postcss-nested': {},
      'postcss-custom-media': {}
    }
  }
})

SFC에서 올바른 문법 하이라이팅을 위해 postcss lang 속성을 사용할 수 있습니다.

<style lang="postcss">
  /* 여기에 postcss를 작성하세요 */
</style>

기본적으로 Nuxt에는 다음 플러그인이 이미 사전 구성되어 있습니다:

여러 스타일을 위한 레이아웃 활용

애플리케이션의 서로 다른 부분을 완전히 다르게 스타일링해야 한다면, 레이아웃을 사용할 수 있습니다. 서로 다른 레이아웃에 서로 다른 스타일을 사용하세요.

<template>
  <div class="default-layout">
    <h1>Default Layout</h1>
    <slot />
  </div>
</template>

<style>
.default-layout {
  color: red;
}
</style>
Read more in Docs > Guide > Directory Structure > Layouts.

서드파티 라이브러리 및 모듈

Nuxt는 스타일링에 대해 의견을 강요하지 않으며, 다양한 옵션을 제공합니다. UnoCSSTailwind CSS와 같은 인기 있는 라이브러리 등 원하는 어떤 스타일링 도구도 사용할 수 있습니다.

커뮤니티와 Nuxt 팀은 통합을 쉽게 해주는 많은 Nuxt 모듈을 개발했습니다. 웹사이트의 modules 섹션에서 찾아볼 수 있습니다. 시작에 도움이 될 만한 몇 가지 모듈은 다음과 같습니다:

  • UnoCSS: 즉시 사용 가능한 원자적 CSS 엔진
  • Tailwind CSS: 유틸리티 우선 CSS 프레임워크
  • Fontaine: 폰트 메트릭 폴백
  • Pinceau: 적응형 스타일링 프레임워크
  • Nuxt UI: 현대 웹 앱을 위한 UI 라이브러리
  • Panda CSS: 빌드 타임에 원자적 CSS를 생성하는 CSS-in-JS 엔진

Nuxt 모듈은 기본적으로 좋은 개발 경험을 제공하지만, 선호하는 도구에 모듈이 없다고 해서 Nuxt에서 사용할 수 없다는 뜻은 아닙니다! 직접 프로젝트에 맞게 설정할 수 있습니다. 도구에 따라 Nuxt 플러그인이나 직접 모듈 만들기가 필요할 수 있습니다. 만들었다면 커뮤니티와 공유하세요!

웹폰트 쉽게 불러오기

Nuxt Google Fonts 모듈을 사용해 Google Fonts를 불러올 수 있습니다.

UnoCSS를 사용하는 경우, 웹폰트 프리셋이 포함되어 있어 Google Fonts 등 주요 제공업체에서 폰트를 편리하게 불러올 수 있습니다.

고급

트랜지션

Nuxt는 Vue와 동일한 <Transition> 요소를 제공하며, 실험적인 View Transitions API도 지원합니다.

Read more in Docs > Getting Started > Transitions.

폰트 고급 최적화

CLS를 줄이기 위해 Fontaine 사용을 권장합니다. 더 고급 기능이 필요하다면, Nuxt 모듈을 만들어 빌드 프로세스나 Nuxt 런타임을 확장하는 것을 고려하세요.

애플리케이션 스타일링을 더 쉽고 효율적으로 만들기 위해 웹 생태계의 다양한 도구와 기법을 적극 활용하세요. 네이티브 CSS, 전처리기, postcss, UI 라이브러리, 모듈 등 무엇을 사용하든 Nuxt가 지원합니다. 즐거운 스타일링 되세요!

LCP 고급 최적화

글로벌 CSS 파일 다운로드 속도를 높이려면 다음을 수행할 수 있습니다:

  • CDN을 사용해 파일을 사용자와 물리적으로 더 가깝게 두세요
  • 자산을 압축하세요, 이상적으로는 Brotli 사용
  • HTTP2/HTTP3로 전송하세요
  • 자산을 동일한 도메인에 호스팅하세요(다른 서브도메인 사용 금지)

이러한 대부분의 작업은 Cloudflare, Netlify, Vercel과 같은 최신 플랫폼을 사용하면 자동으로 처리됩니다. LCP 최적화 가이드는 web.dev에서 확인할 수 있습니다.

모든 CSS가 Nuxt에 의해 인라인된다면, (실험적으로) 렌더된 HTML에서 외부 CSS 파일 참조를 완전히 중지할 수 있습니다. 이것은 후크를 사용해, 모듈이나 Nuxt 설정 파일에 넣어 달성할 수 있습니다.

nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    'build:manifest': (manifest) => {
      // 앱 엔트리, css 리스트 찾기
      const css = Object.values(manifest).find(options => options.isEntry)?.css
      if (css) {
        // 배열의 끝에서 시작해 처음까지 이동
        for (let i = css.length - 1; i >= 0; i--) {
          // 'entry'로 시작하면 리스트에서 제거
          if (css[i].startsWith('entry')) css.splice(i, 1)
        }
      }
    },
  },
})