Concept 1. Auto-imports
Nuxt는 컴포넌트, 컴포저블, 헬퍼함수 및 Vue API 를 자동으로 임포트 한다.
간단한 예를 들면 아래와 같다.
app.vue
<!--
app.vue
--?
<script setup lang="ts">
const count = ref(1) // ref 함수는 선언부 없이 자동으로 가져왔다.
</script>
위에서 자동 임포트의 공식적인 대상은 component/, composable/, utils/ 디렉토리에 있는 소스들이다. 고전적인 전역선언과 달리 입력, IDE 자동완성 및 힌트를 유지하면서 프러덕션 코드에 사용되는 내용만 포함한다.
API 섹션에서 자동으로 임포트한 요소에 대한 참조를 찾을 수 있다.
내장된 Auto-imports
Nuxt는 데이터 호출, 앱컨텍스트, 런타임 구성에 접근할 수 있으며, 상태를 관리하거나 컴포넌트 및 플러그인을 정의하기 위한 함수 및 컴포저블을 자동 임포트한다.
<script setup lang="ts">
/*
useAsyncData(), fetch() 함수가 자동으로 임포트 되었다.
*/
const { data, refresh, pending } = await useFetch('/api/hello')
</script>
ref 또는 computed 같은 Vue3 의 리액티비티 API 뿐만 아니라, 생명주기 훅, 헬퍼 같은 Nuxt 에서 가져온 함수들도 자동 임포트 된다.
<script setup lang="ts">
/*
ref(), computed() 는 자동 임포트 되었다
*/
const count = ref(1)
const double = computed(() => count.value * 2)
</script>
Vue 및 Nuxt 컴포저블
Vue 및 Nuxt에서 제공하는 내장 Composition API 를 사용하는 경우 이들 중 다수가 올바른 컨텍스트 에서 호출되어야 한다는 점에 주의해야한다.
컴포넌트 수명 주기 동안 Vue는 전역 변수를 통해 현재 컴포넌트의 임시 인스턴스를 추적한 다음(마찬가지로 Nuxt는 임시 nuxtApp 인스턴스를 추적함) 동일한 틱에서 설정을 해제한다. 이는 요청 간 상태 오염(두 사용자 간의 공유 참조 누출)을 방지하고 서로 다른 컴포넌트 간의 누출을 방지하기 위해 서버 렌더링 시 필수적이다.
즉, (극소수의 예외를 제외하고) Nuxt 플러그인, Nuxt 경로 미들웨어 또는 Vue setup 함수 외부에서는 사용할 수 없고. 동기식으로 사용해야 한다. 즉, <script setup> 블록 내, defineNuxtComponent로 선언된 컴포넌트의 setup 함수 내, defineNuxtPlugin 또는 defineNuxtRouteMiddleware를 제외하고 컴포저블을 호출하기 전에 await 를 사용할 수 없다. await 후에도 동기 컨텍스트를 유지하기 위해 변환을 수행한다.
"Nuxt instance is unavailableVue" 오류 메시지가 표시되면 또는 Nuxt 수명 주기의 잘못된 위치에서 Nuxt 컴포저블을 호출하고 있다는 의미일 수 있다.
참고) 비동기 함수 내에서 Nuxt 컴포저블을 사용하기 위한 실험적인 기능 asyncContext 에 대해 알아보자.
잘못된 코드의 예:
// composables/example.ts
// 런타임에 대한 참조를 밖에서 하기 때문에 잘못됨.
const config = useRuntimeConfig()
export const useMyComposable = () => {
// accessing runtime config here
}
잘된 코드의 예:
// composables/example.ts
export const useMyComposable = () => {
// useRuntimeConfig 컴포저블 함수가 생명주기 셋업함수 안에 있기 때문에 올바른 코드
const config = useRuntimeConfig()
// ...
}
디렉토리 기반 Auto-imports
아래 디렉토리에 있는 파일들을 자동 임포트한.
- components/ : Vue components
- composable/ : Vue composables
- utils/ : 헬퍼 함수 및 유틸리티
명시적 Imports
Nuxt는 필요한 경우 #imports 앨리어스를 사용하여 자동으 임포트 할 수 있다.
<script setup lang="ts">
import { ref, computed } from '#imports'
const count = ref(1)
const double = computed(() => count.value * 2)
</script>
Auto-imports 비활성
자동 임포트 컴포저블 및 유틸리티를 사용 중지하려면 nuxt.config 파일 에서 imports.autoImport을 false 로 설정하면 된다.
nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
imports: {
autoImport: false
}
})
이렇게 하면 자동 임포트는 비활성화 되지만 명시적으로 선언한 #imports 가져오기는 활성화 된다.
자동 임포트 되어지는 컴포넌트들
Nuxt는 자동으로 가져오는 컴포저블 및 유틸리티 기능과 별도로 ~/components 디렉터리에서 컴포넌트를 자동으로 임포트한다. 컴포넌트 항목을 보면 자세히 알 수 있다.
~/components 디렉토리의 컴포넌트를 자동으로 임포트 하기싫다면 설정 파일에서 components.dir 값을 빈 배열로 선언하면 된다.
nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
components: {
dirs: []
}
})
써드파티 패키지를 자동 임포트 하기
Nuxt는 써드파티 패키지에 대해서도 자동 임포트를 지원한다.
해당 패키지에 Nuxt 모듈을 사용하는 경우, 모듈이 이미 해당 패키지에 대한 자동 임포트를 구성했을 가능성이 높다.
예를 들어 다음과 같이 vue-i18n 패키지에서 useI18n 컴포저블을 자동 임포트 하려면 아래와 같이 한다.
nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
imports: {
presets: [
{
from: 'vue-i18n',
imports: ['useI18n']
}
]
}
})
Concept 2. Vue.js 개발
Nuxt 는 Vue.js를 기본으로 사용하고, SSR 친화적인 환경을 위해 컴포넌트 자동 가져오기, 파일기반 라우팅 및 컴포저블과 같은 기능을 추가하고 있다.
Nuxt는 사용자를 위한 새로운 패턴을 지원하는 Vue의 새로운 주요 릴리스인 Vue 3를 통합하고있다.
따라서, vuejs.org 에서 제공하는 몇가지 지식을 알 필요가 있다.
Nuxt는 항상 Vue를 프론트엔드 프레임워크로 사용해 왔다.
다음과 같은 이유로 Vue 위에 Nuxt가 탄생되었다:
- 데이터 변경이 UI 변경을 트리거하는 Vue의 반응성 모델.
- 컴포넌트 기반 템플릿은 HTML이라는 웹의 공통 언어로 유지하면서 직관적인 패턴을 사용하여 UI를 일관적이면서도 강력하게 유지한다.
- 소규모 프로젝트부터 대규모 웹 애플리케이션에 이르기까지 Vue는 애플리케이션이 사용자에게 계속 가치를 제공할 수 있도록 대규모 성능을 유지한다.
Vue (Nuxt 와 함께할때의 특징)
싱글 파일 컴포넌트(SFC)
*.vue 파일은 템플릿을 위한 <template>태그, 로직을 위한 <script> 태그, 스타일링을 위한 <style> 태그로 구성된.
Nuxt 는 개발자 경험을 위한 핫모듈 교체(HMR)를 별다른 구성없이 지원한다.
자동 임포트
Nuxt 프로젝트 components/ 디렉토리 에 생성된 모든 Vue 컴포넌트는 임포트 없이 프로젝트에서 사용할 수 있다. 컴포넌트가 어디에도 사용되지 않으면 프로덕션 코드에 해당 컴포넌가 포함되지 않는다.
Vue 라우터
대부분의 애플리케이션에는 페이지 간을 탐색할 수 있는 방법이 필요하다. 이것을 라우팅 이라고 하는데, Nuxt는 공식 Vue Router 라이브러리를 사용하여 파일에 매핑된 경로를 직접 생성하기 위해 pages/ 디렉토리 및 명명 규칙을 사용한다.
Nuxt2 / Vue 2 와의 차이점
Nuxt 3는 Vue 3을 기반으로 한다. 새로운 주요 Vue 버전에는 Nuxt가 활용하는 몇 가지 변경 사항이 도입되었다.
- 더 나은 성능
- 컴포지션 API
- 타입스크립트 지원
더 빠른 렌더링
VDOM(Vue Virtual DOM)은 처음부터 다시 작성되었으며 더 나은 렌더링 성능을 제공한다. 또한 컴파일된 단일 파일 컴포넌로 작업할 때 Vue 컴파일러는 정적 마크업과 동적 마크업을 분리하여 빌드 시 이를 더욱 최적화할 수 있다.
이로 인해 첫 번째 렌더링(컴포넌트 생성) 및 업데이트 속도가 빨라지고 메모리 사용량이 줄어든다. Nuxt 3에서는 더 빠른 서버 측 렌더링도 가능하다.
더 작은 번들
Vue 3 및 Nuxt 3에서는 번들 크기 감소에 중점을 두었다. 버전 3에서는 템플릿 지시문 및 내장 컴포넌트를 포함한 대부분의 Vue 기능이 트리 셰이크 가능하다. 사용하지 않으면 프로덕션 번들에 포함되지 않는다.
이런 방식으로 최소 Vue 3 애플리케이션을 gzip으로 압축하면 12kb로 줄일 수 있다.
컴포지션 API
vue 2의 컴포넌에 데이터와 논리를 제공하는 유일한 방법은 옵션 API를 사용하는 것이었다. 이를 통해 data 및 methods 와 같은 사전 정의된 속성을 사용하여 데이터와 메서드를 템플릿에 반환할 수 있었다.
// vue 2 방식
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment(){
this.count++
}
}
}
</script>
Vue 3에 도입된 Composition API는 Options API를 대체하지는 않지만 애플리케이션 전체에서 재사용 가능한 로직을 가능하게 하며 복잡한 컴포넌트의 문제에 따라 코드를 그룹화하는 보다 자연스러운 방법을 제공한다.
위의 vue2 기반의 컴포넌트는 <script> 태그 정의에서 setup 키워드와 함께 Composition API 및 Nuxt 3의 컴포지션 API 와 자동 임포트된 리액티비티 API로 다시 작성되었다.
components/Counter.vue
<!-- compoents/Counter.vue -->
<script setup lang="ts">
const count = ref(0)
const increment = () => count.value++
</script>
Nuxt 3의 목표는 Composition API에 대한 훌륭한 개발자 경험을 제공하는 것이다.
- Vue 및 Nuxt 3 내장 컴포저블 에서 자동으로 가져온 리액티비티 함수를 사용한다 .
- 자동으로 임포트된 재사용 가능한 함수를 composables/ 디렉토리에 작성해야 한다.
타입스크립트 지원
Vue 3과 Nuxt 3는 모두 TypeScript로 작성되었다. 완전히 형식화된(Typed) 코드베이스는 실수를 방지하고 API 사용을 문서화한다. 이는 애플리케이션을 활용하기 위해 TypeScript로 애플리케이션을 작성해야만 한다는 의미는 아니다. Nuxt 3를 사용하면 파일 이름을 .js 에서 .ts 로 바꾸거나컴포넌트 내에서 <script setup lang="ts"> 를 추가하여 선택할 수 있다.
Concept 3. 렌더링 모드
Nuxt 에서 사용할 수 있는 다양한 렌더링 모드에 대해서 알아보자.
Nuxt는 다양한 렌더링 모드, 즉, 유니버셜(univeral) 렌더링(서버 + 클라이언트 렌더링, SSR), 클라이언트 측 렌더링(CSR)을 지원 하지만 하이브리드 렌더링 및 CDN 엣지 서버 에서 애플리케이션을 렌더링할 수 있는 가능성도 제공된다.
브라우저와 서버 모두 JavaScript 코드를 해석하여 Vue.js 컴포넌트를 HTML 요소로 바꿀 수 있다. 이 단계를 렌더링이라고 한다. Nuxt는 범용 렌더링 과 클라이언트 측 렌더링을 모두 지원한다. 두 가지 접근 방식에는 장점과 단점이 있으며 이에 대해 다루겠다.
기본적으로 Nuxt는 더 나은 사용자 경험과 성능을 제공하고 검색 엔진 색인을 최적화하기 위해 범용 렌더링을 사용하지만 한 줄의 구성으로 렌더링 모드를 전환할 수 있다.
유니버셜 렌더링
브라우저가 범용(서버 측 + 클라이언트 측) 렌더링이 활성화된 URL을 요청하면 서버는 완전히 렌더링된 HTML 페이지를 브라우저에 반환한다. 페이지가 미리 생성되어 캐시되었거나 즉시 렌더링되었는지 여부에 관계없이 어느 시점에서 Nuxt는 서버 환경에서 JavaScript(Vue.js) 코드를 실행하여 HTML 문서를 생성했을 것이다. 사용자는 클라이언트 측 렌더링과 달리 애플리케이션의 콘텐츠를 즉시 얻는다. 이 단계는 PHP 또는 Ruby 애플리케이션에서 수행되는 기존 서버 측 렌더링(Jsp, Asp, Thymeleaf 등 포함) 과 유사하다.
동적 인터페이스 및 페이지 전환과 같은 클라이언트 측 렌더링 방법의 이점을 잃지 않기 위해 클라이언트(브라우저)는 HTML 문서가 다운로드되면 백그라운드에서 서버에서 실행되는 JavaScript 코드를 로드한다. 브라우저는 이를 다시 해석하고(따라서 Universal 렌더링) Vue.js는 문서를 제어하고 상호작용을 활성화 하게된다.
브라우저에서 정적 페이지를 대화형으로 만드는 것을 "하이드레이션" 이라고 한다.
유니버셜 렌더링을 사용하면 Nuxt 애플리케이션이 클라이언트 측 렌더링의 이점을 유지하면서 빠른 페이지 로드 시간을 제공할 수 있다. 또한 콘텐츠가 HTML 문서에 이미 존재하므로 크롤러는 오버헤드 없이 콘텐츠를 색인화할 수 있다.
서버 사이드 렌더링의 이점:
- 성능 : 브라우저는 JavaScript로 생성된 콘텐츠보다 훨씬 빠르게 정적 콘텐츠를 표시할 수 있으므로 사용자는 페이지 콘텐츠에 즉시 액세스할 수 있다. 동시에 Nuxt는 하이드레이션 프로세스가 발생할 때 웹 애플리케이션의 상호 작용을 유지한다.
- 검색 엔진 최적화 : 유니버설 렌더링은 페이지의 전체 HTML 콘텐츠를 클래식 서버 애플리케이션으로 브라우저에 전달한다. 웹 크롤러는 페이지 콘텐츠를 직접 인덱싱할 수 있으므로 신속하게 인덱싱하려는 모든 콘텐츠에 대해 범용 렌더링이 탁월한 선택이된다.
유니버설 렌더링은 매우 다재다능하며 거의 모든 사용 사례에 적합하며 특히 블로그, 마케팅 웹사이트, 포트폴리오, 전자상거래 사이트, 마켓플레이스 등 콘텐츠 중심 웹사이트에 적합하다.
브라우저 API에 의존하는 라이브러리를 가져올 때 이를 가져오는 컴포넌트가 클라이언트 측에서만 호출되는지 확인하자. 번들러는 부작용(서버사이드 렌더링에서 브라우저에 의존하는 API 참조)이 포함된 모듈의 가져오기를 트리셰이크하지 않는다.
클라이언트 사이드 렌더링
기본적으로 기존 Vue.js 애플리케이션은 브라우저(또는 클라이언트 )에서 렌더링된다. 그런 다음 Vue.js는 브라우저가 현재 UI를 생성하기 위한 지침이 포함된 모든 JavaScript 코드를 다운로드하고 구문 분석한 후 HTML 요소를 생성한다.
클라이언트사이드 렌더링의 이점:
- 개발 속도 : 전적으로 클라이언트 측에서 작업할 때 window 객체와 같은 브라우저 전용 API를 사용하는 등 코드의 서버 호환성에 대해 걱정할 필요가 없다.
- 저렴함 : 서버 사이드에서 실행하면 JavaScript를 지원하는 플랫폼에서 실행하는 데 필요한 인프라 비용이 추가된다(Nodejs 서버 필요). 반면 클라이언트 사이드 렌더링에서는 HTML, CSS 및 JavaScript 파일을 사용하여 모든 정적 서버에서 클라이언트 전용 애플리케이션을 호스팅할 수 있다.
- 오프라인 : 코드가 완전히 브라우저에서 실행되기 때문에 인터넷을 사용할 수 없는 동안에도 계속 작동할 수 있다.
클라이언트 사이드 렌더링의 단점:
- 성능 : 사용자는 브라우저가 JavaScript 파일을 다운로드하고 구문 분석하고 실행할 때까지 기다려야 한다. 다운로드 부분의 네트워크와 구문 분석 및 실행을 위한 사용자 장치에 따라 시간이 다소 걸릴 수 있으며 사용자 경험에 영향을 미칠 수 있다.
- 검색 엔진 최적화 : 클라이언트 측 렌더링을 통해 전달된 콘텐츠를 인덱싱하고 업데이트하는 것은 서버에서 렌더링된 HTML 문서를 사용할 때보다 시간이 더 걸린다. 이는 우리가 논의한 성능 단점과 관련이 있다. 검색 엔진 크롤러는 페이지 색인을 처음 시도할 때 UI가 완전히 렌더링될 때까지 기다리지 않기 때문이다. 순수 클라이언트 측 렌더링을 사용하면 콘텐츠가 검색 결과 페이지에 표시되고 업데이트되는 데 더 많은 시간이 걸린다.
클라이언트 측 렌더링 방식은 인덱싱이 필요하지 않거나 사용자가 자주 방문하는 대화형 웹 애플리케이션에 적합한 선택이다. 이를 보완하기 위해 브라우저 캐싱을 활용하여 SaaS, 백오피스 애플리케이션 또는 온라인 게임과 같은 후속 방문 시 다운로드 단계를 건너뛸 수 있다.
Nuxt의 nuxt.config.ts 사용하여 클라이언트 사이드 전용 렌더링을 활성화할 수 있다.
// nuxt.config.ts
export default defineNuxtConfig({
ssr: false
})
ssr: false 를 사용하는 경우 앱이 하이드레이션될 때까지 렌더링하는데 사용할 일부 HTML과 함께
HTML 파일을 "~/app/spa-loading-template.html" 에 배치해야한다 .
하이브리드 렌더링
하이브리드 렌더링은 라우팅 규칙을 사용하여 경로별로 다른 캐싱 규칙을 허용 하고 서버가 지정된 URL의 새 요청에 응답하는 방법을 결정한다.
이전에는 Nuxt 애플리케이션과 서버의 모든 route/page 가 유니버셜이든 클라이언트 측이든 동일한 렌더링 모드를 사용해야 했다. 다양한 경우에 일부 페이지는 빌드 시 생성될 수 있지만 다른 페이지는 클라이언트 측에서 렌더링되어야 한다. 예를 들어 어드민 섹션이 있는 콘텐츠 웹사이트를 생각해 보자. 모든 콘텐츠 페이지는 기본적으로 정적이어야 하며 한 번 생성되어야 하지만 어드민 섹션은 등록이 필요하며 동적 애플리케이션처럼 작동해야한다.
Nuxt 3에는 라우팅 규칙 및 하이브리드 렌더링 지원이 포함되어 있다. 라우팅 규칙을 사용하면 라우팅 그룹에 대한 규칙을 정의하고, 렌더링 모드를 변경하거나 경로를 기반으로 캐시 전략을 할당할 수 있다.
Nuxt 서버는 Nitro 캐싱 레이어를 사용하여 해당 미들웨어 및 경로를 자동으로 등록한다 .
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Homepage pre-rendered at build time
'/': { prerender: true },
// Product page generated on-demand, revalidates in background
'/products/**': { swr: 3600 },
// Blog post generated on-demand once until next deploy
'/blog/**': { isr: true },
// Admin dashboard renders only on client-side
'/admin/**': { ssr: false },
// Add cors headers on API routes
'/api/**': { cors: true },
// Redirects legacy urls
'/old-page': { redirect: '/new-page' }
}
})
라우팅 규칙
사용할 수 있는 다양한 속성은 다음과 같다.
- redirect: string - 서버 측 리디렉션을 정의한다.
- ssr: boolean - 앱 섹션에 대한 서버 측 렌더링을 비활성화하고 ssr: false 사용하여 SPA 전용으로 만든다.
- cors: boolean - cors: true 자동으로 cors 헤더를 추가. - headers 재정의하여 ouput을 사용자 정의한다.
- headers: object - 사이트의 섹션에 특정 헤더(예: assets)를 추가한다.
- swr: number|boolean - 서버 응답에 캐시 헤더를 추가하고 구성 가능한 TTL(Time to Live)을 위해 서버 또는 리버스 프록시에 캐시한다. Nitro의 사전 설정 은 node-server 전체 응답을 캐시할 수 있다. TTL이 만료되면 페이지가 백그라운드에서 재생성되는 동안 캐시된 응답이 전송된다. true를 사용하면 MaxAge 없이 stale-while-revalidate 헤더가 추가됨.
- isr: number|boolean - 이를 지원하는 플랫폼(현재 Netlify 또는 Vercel)에서 CDN 캐시에 응답을 추가할 수 있다는 점을 제외하면 동작은 swr 과 동일하다 . true 를 사용하는 경우 콘텐츠는 CDN 내부에 다음에 배포될 때까지 유지된다.
- prerender:boolean - 빌드 시 경로를 사전 렌더링하고 이를 정적 자산으로 빌드에 포함된다.
- experimentalNoScripts: boolean - 사이트 섹션에 대한 Nuxt 스크립트 및 JS 리소스 힌트 렌더링을 비활성화한다.
가능할 때마다 라우팅 규칙은 최적의 성능을 위해 배포 플랫폼의 기본 규칙에 자동으로 적용다(현재 Netlify 및 Vercel이 지원됨).
nuxt generate 를 사용하는 경우 하이브리드 렌더링을 사용할 수 없다.
하이드브리드 렌더링의 예 : https://github.com/danielroe/nuxt-vercel-isr
Edge-Side 렌더링
ESR(Edge-Side Rendering)은 Nuxt 3에 도입된 강력한 기능으로, CDN(Content Delivery Network)의 에지 서버를 통해 Nuxt 애플리케이션을 사용자에게 더 가깝게 렌더링할 수 있다. ESR을 활용하면 성능 향상과 대기 시간 감소를 보장하여 향상된 사용자 경험을 제공할 수 있다.
ESR을 사용하면 렌더링 프로세스가 네트워크의 '에지', 즉 CDN의 에지 서버로 푸시된다. ESR은 실제 렌더링 모드라기보다는 배포 대상에 가깝다.
페이지에 대한 요청이 이루어지면 원래 서버까지 가는 대신 가장 가까운 에지 서버에서 이를 가로챈다. 이 서버는 페이지의 HTML을 생성하여 사용자에게 다시 보내고, 이 프로세스는 데이터가 이동해야 하는 물리적 거리를 최소화하여 대기 시간을 줄이고 페이지를 더 빠르게 로드한다.
Nuxt 3를 구동하는 서버 엔진인 Nitro 덕분에 엣지 사이드 렌더링이 가능하다 . Node.js, Deno, Cloudflare Workers 등에 대한 크로스 플랫폼을 지원한다.
현재 ESR을 활용할 수 있는 플랫폼은 다음과 같다.
- git 통합 및 nuxt build 명령을 사용하여 구성이 전혀 없는 Cloudflare 페이지
- NITRO_PRESET=lagon npx nuxt build 명령을 사용하는 Lagon
- nuxt build 명령과 NITRO_PRESET=vercel-edge 환경 변수를 사용하는 Vercel Edge Functions
- nuxt build 명령과 NITRO_PRESET=netlify-edge 환경 변수를 사용하는 Netlify Edge Functions
라우팅 규칙과 함께 Edge-Side Rendering을 사용할 때 하이브리드 렌더링을 사용할 수 있다 .
궁금하면 위에 언급된 일부 플랫폼에 배포된 오픈 소스 예제를 탐색할 수 있다.
Concept 4. 서버 엔진
Nuxt 3를 구축되는 동안 Nitro 라는 새로운 서버 엔진이 만들어졌다.. 기능은 다음과 같다.
- Node.js, 브라우저, 서비스 작업자 등에 대한 크로스 플랫폼 지원.
- 즉시 사용 가능한 서버리스 지원.
- API 라우트 지원.
- 자동 코드 분할 및 비동기 로드 청크.
- 정적 + 서버리스 사이트를 위한 하이브리드 모드.
- 핫 모듈을 다시 로드하는 개발 서버.
API 레이어
서버 API 엔드포인트와 미들웨어는 내부적으로 h3 을 사용하는 Nitro에 의해 추가된다. 주요기능은 다음과 같다.
- 핸들러는 자동으로 처리되는 JSON 응답에 대해 객체/배열을 직접 반환할 수 있다.
- 핸들러는 await ( res.end(), next() 지원) 하고 promise 를 반환할 수 있다.
- body 파싱, 쿠키 처리, 리디렉션, 헤더 등을 위한 헬퍼 기능.
힌트) API layer 는 server/ 디렉토리에 구성되면, 자세한 내용은 여기를 참고하자.
직접 API 호출
Nitro는 전역적으로로 사용 가능한 $fetch 헬퍼를 통해 API 경로를 '직접' 호출할 수 있다 . 브라우저에서 실행하면 서버에 API를 호출하지만, 서버에서 실행하면 관련 함수를 직접 호출하여 추가 API 호출을 저장다 .
$fetchAPI는 다음과 같은 주요 기능과 함께 ofetch를 사용한.
- JSON 응답의 자동 파싱(필요한 경우 원시 응답에 액세스 가능)
- 요청 본문과 매개변수는 올바른 Content-Type 헤더와 함께 자동으로 처리된다.
$fetch 기능에 대한 자세한 내용은 ofetch를 확인하자.
형식화된(Typed) API 라우트
API 라우트 (또는 미들웨어)를 사용할 때 응답을 보낼 때 res.end() 을 사용하는대신 값(value)을 반환하는 경우 Nitro는 이러한 경로에 대한 입력을 생성한다 .
$fetch() 또는 useFetch() 를 사용할 때 이러한 유형에 액세스할 수 있다.
독립형 서버
Nitro 는 node_modules 에 의존하지 않는 독립형 서버를 제공한다.
Nuxt 2의 서버는 독립형이 아니며 실행(배포판 포함) 또는 사용자 정의 프로그래밍 방식 사용에 관여하려면 Nuxt 코어의 일부가 필요하다 . nuxt start( nuxt-start 혹은 nuxt) 는 취약하고 파손되기 쉬우며 서버리스 및 서비스 작업자 환경에 적합하지 않다.
Nuxt 3는 nuxt build 실행될 때 .output 디렉토리로 dist를 생성한다.
출력에는 모든 환경(실험적 브라우저 서비스 워커 포함!)에서 Nuxt 서버를 실행하고 정적 파일을 제공하는 런타임 코드가 포함되어 있어 JAMstack을 위한 진정한 하이브리드 프레임워크가 된다. 더해서 Nuxt는 멀티 소스 드라이버와 로컬 assets을 지원하는 기본 스토리지 계층을 구현한다.
Concept 5. 모듈
Nuxt 는 프레임워크 코어를 확장하고 통합을 단순화하는 모듈 시스템을 제공한다.
Nuxt 모듈 탐색
Nuxt를 사용하여 프로덕션급 애플리케이션을 개발할 때 프레임워크의 핵심 기능이 충분하지 않다는 것을 알 수 있다. Nuxt는 구성 옵션 및 플러그인을 사용하여 확장할 수 있지만 여러 프로젝트에서 이러한 사용자 정의를 유지하는 것은 지루하고 반복적이며 시간이 많이 걸릴 수 있다. 반면, 모든 프로젝트의 요구 사항을 즉시 지원하는 것은 Nuxt를 매우 복잡하고 사용하기 어렵게 만들 것이다.
이것이 Nuxt가 코어 확장이 가능한 모듈 시스템을 제공하는 이유 중 하나다. Nuxt 모듈은 nuxi build 을 사용하여 프로덕션용 프로젝트를 빌드하거나 개발 모드에서 nuxi dev 를 이용해 Nuxt 를 시작할 때 순차적으로 실행되는 비동기 함수다 . 템플릿을 재정의하고, 웹팩 로더를 구성하고, CSS 라이브러리를 추가하고, 기타 여러 유용한 작업을 수행할 수 있다.
무엇보다도 Nuxt 모듈은 npm 패키지로 배포할 수 있다. 이를 통해 프로젝트 전체에서 재사용하고 커뮤니티와 공유할 수 있어 고품질 추가 기능 생태계를 만드는 데 도움이 된다.
Nuxt 모듈 추가
모듈을 설치한 후에는 nuxt.config.ts 파일 modules 속성에 이를 추가할 수 있다. 모듈 개발자는 일반적으로 사용에 대한 추가 단계와 세부 정보를 제공해야한다.
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
// Using package name (recommended usage)
'@nuxtjs/example',
// Load a local module
'./modules/example',
// Add module with inline-options
['./modules/example', { token: '123' }]
// Inline module definition
async (inlineOptions, nuxt) => { }
]
})
주의) Nuxt 모듈은 이제 빌드 타임 전용이며 Nuxt 2에서 사용된 속성은 buildModules 는 더 이상 사용되지 않고 modules 속성으로 대체되었다.
Nuxt 모듈 생성
모듈 생성과 관련해서 가이드 문서를 참조한다.
Concept 6. ES 모듈
Nuxt 3 및 브릿지는 기본 ES 모듈을 사용한다.
이 장은 Nuxt 앱(또는 업스트림 라이브러리)을 ESM과 호환되게 만드는 방법을 아는데 도움이 될것이다.
배경
CommonJS 모듈
CommonJS(CJS)는 격리된 JavaScript 모듈 간에 기능을 공유할 수 있도록 Node.js에 도입된 형식다. 다음 구문에 이미 익숙할 수도 있을 것이다.
const a = require('./a')
module.exports.a = a
webpack 및 Rollup과 같은 번들러는 이 구문을 지원하며 브라우저에서 CommonJS로 작성된 모듈을 사용할 수 있다.
ESM 구문
대부분의 경우 사람들이 ESM과 CJS에 대해 이야기할 때 모듈 작성을 위한 다른 구문에 대해 이야기한다.
import a from './a'
export { a }
ESM(ECMAScript 모듈)이 표준이 되기 전에(10년 이상이 걸렸다!) webpack 과 같은 도구와 심지어 TypeScript와 같은 언어도 소위 ESM 구문을 지원하기 시작했다. 그러나 실제 사양에는 몇 가지 주요 차이점이 있다. 여기에 유용한 설명이 있다 .
'네이티브' ESM이란 무엇일까?
오랫동안 ESM 구문을 사용하여 앱을 작성해 왔을지도 모른다. 결국 이는 브라우저에서 기본적으로 지원되며 Nuxt 2에서는 작성한 모든 코드를 적절한 형식(서버용 CJS, 브라우저용 ESM)으로 컴파일했다.
패키지에 모듈을 추가할 때 상황이 약간 달라졌. 샘플 라이브러리는 CJS 및 ESM 버전을 모두 노출할 수 있으며 원하는 버전을 선택할 수 있다.
{
"name": "sample-library",
"main": "dist/sample-library.cjs.js",
"module": "dist/sample-library.esm.js"
}
따라서 Nuxt 2에서는 번들러(webpack)가 서버 빌드를 위해 CJS 파일('main')을 가져오고 클라이언트 빌드를 위해 ESM 파일('module')을 사용한다.
그러나 최근 Node.js LTS 릴리스에서는 이제 Node.js 내에서 기본 ESM 모듈을 사용할 수 있다. 즉, Node.js 자체는 ESM 구문을 사용하여 JavaScript를 처리할 수 있지만 기본적으로는 처리하지 않는다. ESM 구문을 활성화하는 가장 일반적인 두 가지 방법은 다음과 같다.
- type: 'module' 을 package.json 내에서 설정 하고 .js 확장자를 유지한다.
- .mjs 파일 확장자 사용 (권장)
우리는 Nuxt Nitro를 위해 .output/server/index.mjs 파일을 작성하면, Node.js가 이 파일을 기본 ES 모듈로 처리하도록 지시한다.
Node.js 컨텍스트에서 유효한 임포트란 무엇입니까?
require 모듈이 아닌 import 모듈을 사용하면 Node.js는 이를 다르게 처리한다. 예를 들어 sample-library 를 임포트 할때 Node.js는 해당 라이브러리 main을 보지않고 라이브러리의 package.json 내에있는 exports 혹은 module 항목을 본다.
이 것은 const b = await import('sample-library') 처럼 동적 임포트를 의미한다.
노드는 다음 종류의 가져오기를 지원한다( 문서 참조 ):
.mjs 로 끝나는 파일 - ESM 구문을 사용해야 한다.
.cjs 로 끝나는 파일 - CJS 구문을 사용해야 한다.
.js 로 끝나는 파일 - package.json 에 type: 'module' 없는 한 CJS 구문을 사용할 것으로 예상된다.
어떤 종류의 문제가 있을 수 있을까?
오랫동안 모듈 작성자는 ESM 구문 빌드를 생성해 왔지만 .esm.js 또는 .es.js 같은 규칙을 package.json 파일의 module 필드에 사용하였다. 파일 확장자를 특별히 신경 쓰지 않는 webpack과 같은 번들러에서만 사용되었기 때문에 지금까지는 문제가 되지 않았다
그러나 Node.js ESM 컨텍스트의 .esm.js 파일에 포함된 패키지를 가져오려고 하면 작동하지 않으며 다음과 같은 오류가 발생한다.
(node:22145) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
/path/to/index.js:1
export default {}
^^^^^^
SyntaxError: Unexpected token 'export'
at wrapSafe (internal/modules/cjs/loader.js:1001:16)
at Module._compile (internal/modules/cjs/loader.js:1049:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
....
at async Object.loadESM (internal/process/esm_loader.js:68:5)
Node.js가 CJS라고 생각하는 ESM 구문 빌드에서도 이 오류가 발생할 수 있다.
file:///path/to/index.mjs:5
import { named } from 'sample-library'
^^^^^
SyntaxError: Named export 'named' not found. The requested module 'sample-library' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'sample-library';
const { named } = pkg;
at ModuleJob._instantiate (internal/modules/esm/module_job.js:120:21)
at async ModuleJob.run (internal/modules/esm/module_job.js:165:5)
at async Loader.import (internal/modules/esm/loader.js:177:24)
at async Object.loadESM (internal/process/esm_loader.js:68:5)
ESM 문제 해결
이러한 오류가 발생하면 업스트림 라이브러리에 문제가 있는 것이 거의 확실하다. Node.js에서 가져오기를 지원하려면 라이브러리를 수정 해야한다.
라이브러리 트랜스파일링
그동안 Nuxt 에 build.transpile. 를 추가하여 이러한 라이브러리를 가져오지 않도록 지시할 수 있었다.
export default defineNuxtConfig({
build: {
transpile: ['sample-library']
}
})
이러한 라이브러리에서 가져오는 다른 패키지도 추가해야 할 수도 있다.
앨리어싱 라이브러리
경우에 따라 라이브러리의 별칭을 CJS 버전에 수동으로 지정해야 할 수도 있다. 예를 들면 다음과 같다.
export default defineNuxtConfig({
alias: {
'sample-library': 'sample-library/dist/sample-library.cjs.js'
}
})
기본 내보내기
CommonJS 형식의 종속성은 기본 내보내기를 제공 하기 위해 module.exports 혹은 exports 를 사용할 수 있다.
// node_modules/cjs-pkg/index.js
module.exports = { test: 123 }
// or
exports.test = 123
이는 일반적으로 require 종속성이 있는 경우 잘 작동한다.
// test.cjs
const pkg = require('cjs-pkg')
console.log(pkg) // { test: 123 }
기본 ESM 모드의 Node.js , esModuleInterop 가 활성화된 TypeScript 및 webpack과 같은 번들러는 이러한 라이브러리를 기본적으로 가져올 수 있도록 호환성 메커니즘을 제공한다. 이 메커니즘을 종종 "interop require default"라고 한다.
import pkg from 'cjs-pkg'
console.log(pkg) // { test: 123 }
그러나 구문 감지 및 다양한 번들 형식의 복잡성으로 인해 항상 interop 기본값이 실패할 가능성이 있으며 결국 다음과 같은 결과가 발생한다.
import pkg from 'cjs-pkg'
console.log(pkg) // { default: { test: 123 } }
또한 (CJS 및 ESM 파일 모두에서) 동적 가져오기 구문을 사용할 때 항상 다음과 같은 상황이 발생한다.
import('cjs-pkg').then(console.log) // [Module: null prototype] { default: { test: '123' } }
이 경우 기본 내보내기를 수동으로 상호 운용해야한다.
// Static import
import { default as pkg } from 'cjs-pkg'
// Dynamic import
import('cjs-pkg').then(m => m.default || m).then(console.log)
보다 복잡한 상황을 처리하고 안전성을 높이기 위해 명명된 내보내기를 보존할 수 있는 Nuxt 3에서 mlly를 내부적으로 사용하는 것을 권장한다.
import { interopDefault } from 'mlly'
// Assuming the shape is { default: { foo: 'bar' }, baz: 'qux' }
import myModule from 'my-module'
console.log(interopDefault(myModule)) // { foo: 'bar', baz: 'qux' }
라이브러리 작성 가이드
좋은 소식은 ESM 호환성 문제를 해결하는 것이 비교적 간단하다는 것이다. 두 가지 주요 옵션이 있다:
- ESM 파일의 이름을 .mjs 로 할것
이는 권장되는 가장 간단한 접근 방식이다. 라이브러리의 종속성 및 빌드 시스템과 관련된 문제를 정리해야 할 수도 있지만 대부분의 경우 이렇게 하면 문제가 해결된다. 또한 명확성을 극대화하기 위해 CJS 파일의 이름을 .cjs 로 끝나도록 바꾸는 것이 좋다 . - 전체 라이브러리를 ESM 전용으로 만들도록 선택할 수 있다 .
이는 package.json 에 type: 'module' 을 설정하고 빌드된 라이브러리가 ESM 구문을 사용하는지 확인하는 것을
의미한다. 이 접근 방식은 라이브러리가 ESM 컨텍스트에서만 사용될 수 있음을 의미하지만, 종속성 문제에 직면할 수 있다.
마이그레이션
CJS에서 ESM으로의 초기 단계는 require 대신 import 의 사용법에 익숙해지는것이다.
// Before
module.exports = ...
exports.hello = ...
// After
export default ...
export const hello = ...
// Before
const myLib = require('my-lib')
// After
import myLib from 'my-lib'
// or
const myLib = await import('my-lib').then(lib => lib.default || lib)
ESM 모듈에서는 CJS와 달리 , require및 require.resolve, __filename, __dirname 을 글로벌로 사용할 수 없으며 import() 및 import.meta.filename 으로로 바꿔야 한다.
// Before
import { join } from 'path'
const newDir = join(__dirname, 'new-dir')
// After
import { fileURLToPath } from 'node:url'
const newDir = fileURLToPath(new URL('./new-dir', import.meta.url))
// Before
const someFile = require.resolve('./lib/foo.js')
// After
import { resolvePath } from 'mlly'
const someFile = await resolvePath('my-lib', { url: import.meta.url })
베스트 프랙티스
- 기본 내보내기보다는 명명된 내보내기를 선호한다. 이는 CJS 충돌을 줄이는 데 도움이된다. (기본 내보내기 섹션 참조)
- Nitro 폴리필 없이도 브라우저 및 Edge Workers에서 라이브러리를 사용할 수 있도록 하려면 Node.js 내장 기능과 CommonJS 또는 Node.js 전용 종속성에 최대한 의존하지 않는다.
- 조건부 내보내기에 새 exports 필드를 사용한다. ( 참조 )
Concept 7. 타입스크립트
Nuxt 는 완벽히 타입을 지원하고 코딩하는 정확한 타입정보에 액세스할 수 있도록 유용한 단축키를 제공한다.
타입 확인
기본적으로 Nuxt는 성능상의 이유로 nuxi dev 또는 nuxi build 를 실행할 때 유형을 확인하지 않는다.
빌드 또는 개발 시 유형 검사를 활성화하려면 vue-tsc 및 typescript 개발 종속성으로 설치해야한다.
yarn add --dev vue-tsc typescript
# or
npm install --save-dev vue-tsc typescript
# or
pnpm add -D vue-tsc typescript
or
bun add -D vue-tsc typescript
그런 다음 nuxi typecheck 령을 실행하여 유형을 확인한다.
npx nuxi typecheck
빌드 시 유형 검사를 활성화하려면 nuxt.confgi 파일에서 typescript.typeCheck 옵션을 사용할 수도 있다.
// nuxt.config.ts
export default defineNuxtConfig({
typescript: {
typeCheck: true
}
})
자동 생성 타입
nuxi dev또는 nuxi build 를 실행하면 Nuxt 는 IDE의 유형 지원(및 유형 확인)을 위해 다음 파일을 생성한다:
.nuxt/nuxt.d.ts
이 파일에는 사용 중인 모든 모듈 유형과 Nuxt 3에 필요한 키 유형이 포함되어 있다. IDE는 이러한 유형을 자동으로 인식해야한다.
파일의 일부 참조는 buildDir( .nuxt ) 내에서만 생성되는 파일에 대한 것이므로 전체 입력을 위해서는 nuxi dev 또는 nuxi build 를 실행해야 한다 .
.nuxt/tsconfig.json
이 파일에는 Nuxt 에서 삽입한 확인된 앨리어스 또는 사용 중인 모듈을 포함하여 프로젝트에 권장되는 기본 TypeScript 구성이 포함되어 있으므로 ~/file 또는 같은 #build/file 앨리어스에 대한 전체 유형 지원 및 경로 자동 완성을 얻을 수 있다.
Nitro는 API 라우트 유형도 자동 생성한. 또한 Nuxt는 전역적으로 사용 가능한 컴포넌트에 대한 타입을 생성하고 컴포저블에서 자동 임포트와 기타 핵심 기능도 생성한다.
./.nuxt/tsconfig.json에서 확장된 모든 옵션은 tsconfig.json 에서 정의된 옵션으로 덮어쓰여진다는 점에 유의하자. "compilerOptions.paths" 구성과 같은 옵션을 덮어쓰면 TypeScript가 ./.nuxt/tsconfig.json 로 인해 인식되지 않는 문제가 발생해 #imports 등이 인식되지 않을 수도 있다. 추가로 제공되는 옵션 ./.nuxt/tsconfig.json 을 확장해야 하는 경우. nuxt.config 안의 alias 프로퍼티를 이용할 수 있다. nuxi는 그 후 ./.nuxt/tsconfig.json 을 적절하게 확장할 것이다.
더욱 엄격한 타입 검사
TypeScript에는 프로그램의 안전성과 분석을 강화하기 위한 특정 검사 기능이 제공된다.
코드베이스를 TypeScript로 변환하고 익숙해지면 더 높은 안전성을 위해 이러한 검사를 활성화할 수 있다( 자세히 읽기 ).
엄격한 유형 검사를 활성화하려면 nuxt.config 을 업데이트 해야한다.
// nuxt.config.ts
export default defineNuxtConfig({
typescript: {
strict: true
}
})
'Nuxt 공식문서 번역 > 개요' 카테고리의 다른 글
6. Styling (1) | 2023.12.10 |
---|---|
5. Assets (1) | 2023.12.10 |
4. Views (1) | 2023.12.10 |
3. 설치 및 구성 (1) | 2023.12.10 |
1. Nuxt 소개 (1) | 2023.12.02 |