features
Assets
- public : server root에서 파일 그대로 브라우저로 전달함.
- assets : build tool에 의해 처리가 됨
Vite나 Webpack의 주기능은 자바스크리트 파일들을 처리하는 것임. 그러나 plugins(Vite)나 loaders(Webpack) 을 통해 다른 형태의 assets들도 처리 함( ex) stylesheets, fonts, SVG 등)
이 작업은 원래의 파일을 주로 성능이나 캐싱을 위한 절차 임( style파일을 최소화, 브라우저 캐시무효화)
Head Management
useHead Composable
ex)
<script setup>
useHead({
titleTemplate: 'My App - %s',
// or, instead:
// titleTemplate: (title) => `My App - ${title}`,
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
charset: 'utf-8',
meta: [
{ name: 'description', content: 'My amazing site.' }
],
bodyAttrs: {
class: 'test'
}
})
</script>
setup function 안에서 useHead메서드를 사용해서 meta속성들(title, titleTemplate, base, script, style, meta, link, htmlAttrs, bodyAttrs)을 커스터마이징 해 사용할 수 있음. charset, viewport속성은 예시와 같이 chartset, viewport key값에 value를 설정해 쉽게 커스텀 할 수 있음
Meta Components
Nuxt는 metadata를 vue component template에서 사용할 수 있게 <Title>, <Base>, <Script>, <Style>, <Meta>, <Link>, <Body>, <Html>and <Head> 컴포넌트를 제공함
<script setup>
const title = ref('Hello World')
</script>
<template>
<div>
<Head>
<Title>{{ title }}</Title>
<Meta name="description" :content="title" />
<Style type="text/css" children="body { background-color: green; }" />
</Head>
<h1>{{ title }}</h1>
</div>
</template>
usage with definePageMeta
현재의 route의 metadata를 설정하기 위해 useHead 메서드와 definePageMeta메서드는 함께 사용가능함
<script setup>
definePageMeta({
title: 'Some Page'
})
</script>
위의 예시는 page에 해당하는 vue파일임
<script setup>
const route = useRoute()
useHead({
meta: [{ name: 'og:title', content: `App Name - ${route.meta.title}` }]
})
</script>
위의 예시는 layout파일에 해당함.
각각 page에 해당하는 파일에 meta 속성을 설정 해놓고, layout 파일에서 route의 현재 route의 메타데이터를이용해 html 데이터를 설정 할 수 있음.
Data Fetching
Nuxt는 데이터를 핸들링 하기 위해 useFetch, useLazyFetch, useAsyncData, useLazyAsyncData, 를 제공함 ⇒ 이 메서드들은 setup이나, lifecycle hook에서만 사용 가능함.****
useAsyncData
page, component, plugin에서 비동기적으로 데이터를 받을 때 useAsyncData를 사용할 수 있음
ex)
let counter = 0
export default () => {
counter++
return JSON.stringify(counter)
}
위의 예시는 server/api/count.ts 파일
<script setup>
const { data } = await useAsyncData('count', () => $fetch('/api/count'))
</script><template>
Page visits: {{ data }}
</template>
count의 data를 받아 올때는 위와 같이 사용함
useLazyAsyncData
useAsyncData에 lazy:true 옵션을 추가한 것과 동일함.
async function 이 페이지 전환을 막지않음. data가 null일때도 data를 핸들링 할 수 있음
<template>
<div>
{{ pending ? 'Loading' : count }}
</div>
</template>
<script setup>
const { pending, data: count } = useLazyAsyncData('count', () => $fetch('/api/count'))
watch(count, (newCount) => {
// Because count starts out null, you won't have access
// to its contents immediately, but you can watch it.
})
</script>
useFetch
page, component, plugin 파일 안에서 useFetch 메서드를 사용 할 수 있음
useFetch는 어느 URL이든 접근하여 데이터를 fetch 할 수 있게 하는 메서드임.
이 메서드는 useAsyncData와 $fetch를 함께 포함하고 있는 개념이기 때문에 사용하기 편리함.
<script setup>
const { data } = await useFetch('/api/count')
</script><template>
Page visits: {{ data.count }}
</template>
useLazyFetch
useFetch에 lazy:true 옵션을 추가한 것과 동일함.
async function이 페이지 전환을 막지 않음
data가 null일때도 핸들링이 가능함.
<template><!-- you'll need to handle a loading state -->
<div v-if="pending">
Loading ...
</div>
<div v-else>
<div v-for="post in posts">
<!-- do something -->
</div>
</div>
</template>
<script setup>
const { pending, data: posts } = useLazyFetch('/api/posts')
watch(posts, (newPosts) => {
// Because posts starts out null, you won't have access
// to its contents immediately, but you can watch it.
})
</script>
Refreshing Data
data 요청을 다시 보내 data를 새로 load해야 하는 겨우. refresh API를 사용할 수 있음.
useAsyncData 를 이용해 refresh 메서드를 받아 사용함
<script setup>
const page = ref(1);
const { data:users, loading, refresh, error } = await useFetch(() => `users?page=${page.value}&take=6`, { baseURL: config.API_BASE_URL }
);
function previous(){
page.value--;
refresh();
}
function next() {
page.value++;
refresh();
}
</script>
refreshNuxtData
useAsyncData, useLazyAsyncData, useFetch, useLazyFetch 의 캐시를 무효화 하고, refetch하기 위한 메서드.
현재 페이지의 모든 data를 새롭게 refresh 할때 사용
<template><div>
{{ pending ? 'Loading' : count }}
</div><button @click="refresh">Refresh</button></template><script setup>
const { pending, data: count } = useLazyAsyncData('count', () => $fetch('/api/count'))
const refresh = () => refreshNuxtData('count')
</script>
Isomorphic fetch and $fetch
브라우저에서 fetch를 사용할때, user의 헤더(ex)cookie)는 바로 API로 보내진다. 그러나 server-side-rendering에서 fetch 요청은 서버 안에서 발생하게 되기 때문에 이는 브라우저의 cookie를 전송 받지 못하고, 응답에도 cookie를 보낼 수 없다.
Pass client headers to the API
이 문제는 useRequestHeaders를 사용해서 server-side의 API에서 cookie를 접근할 수 있음
<script setup>
const { data } = useFetch('/api/me', {
headers: useRequestHeaders(['cookie'])
})
</script>
위는 브라우저에서 cookie를 받아 사용하는 예시
Pass on cookies from server-side API calls on SSR response
만약 cookie를 반대로 응답에 포함하여 전달하고 싶을 때는 아래 예시와 같이 사용
export const fetchWithCookie = async (url: string, cookieName: string) => {
const response = await $fetch.raw(url)
if (process.server) {
const cookies = Object.fromEntries(
response.headers.get('set-cookie')?.split(',').map((a) => a.split('='))
)
if (cookieName in cookies) {
useCookie(cookieName).value = cookies[cookieName]
}
}
return response._data
}
<script setup lang="ts">
// This composable will automatically pass on a cookie of our choice.
const result = await fetchWithCookie("/api/with-cookie", "test")
onMounted(() => console.log(document.cookie))
</script>
Using async setup
async setup() composable에는 처음 await를 사용하고 나서 현재 component의 instance가 손실됨.
따라서 useFetch같은 메서드를 여러번 호출 하기 위해서는 <script setup>을 사용하거나 함께 묶어서 await를 걸어야 함
State Management
useState
SSR-frendly한 반응형을 만들기 위해 Nuxt는 useState라는 composable을 제공함.
이 메서드를 이용해 만든 value는 server-side 렌더링 이후에도 보존됨.
모든 컴포넌트들에서 공유됨.
Basic usage
<script setup>
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>
<template>
<div>
Counter: {{ counter }}
<button @click="counter++">
+
</button><button @click="counter--">
-
</button>
</div>
</template>
위 예시는 component-local counter state를 사용하고 있음. 하지만 다른 컴포넌트들 역시 useState(’counter’)로 같은 반응형 state를 공유한다.
Error Handling
Nuxt3는 full-stack framework로 예측불허한 다양한 런타임 에러들이 다양한 context에서 발생할 수 있음.
- Errors during the Vue rendering lifecycle(SSR + SPA)
- Errors during API or Nitro server lifecycle
- Server and client startup errors(SSR + SPA)
Errors during the Vue rendering lifecycle (SSR + SPA)
Vue 에러를 onErrorCaptured 에서 핸들링 할수 있음
또한 Nuxt는 에러가 발생하면 상위 레벨로 에러를 전파 시켜 vue:error 훅이 호출 됨
vueApp.config.errorHandler에서 핸들링 가능한 모든 Vue에러를 받을 수 있음
Server and client startup errors (SSR + SPA)
Nuxt는 Nuxt 애플리케이션이 시작할 때 에러가 발생하면 app:error 훅을 호출한다.
이는
- plugin을 실행할 때
- app:created, app:beforeMount 훅을 처리할 때
- app이 mount될때의 에러는 onErrorCaptured 나 vue:error 훅에서 처리해야함
- app:mounted 훅을 처리할 때
호출된다.
Errors during API or Nitro server lifecycle
server-side에서 handler를 지정 할 수는 없지만, 에러 페이지를 띄울 수 있음.
Rendering an error page
Nuxt가 렌더링 시 client side나 server side lifecycle에서 에러를 만나면 JSON 응답 또는 HTML 에러 페이지를 렌더링 함(요청 헤더에 Accept:application/json 이 있다면)
이 에러 페이지는 app.vue와 같은 레벨의 source directory의 ~/error.vue로 만들어서 커스터마이징 할 수 있다. 만약 error 페이지를 지우고 싶다면 clearError helper 함수로 다른 페이지로 에러 발생시 리다이렉트 시킬 수 있다.
<template>
<button @click="handleError">Clear errors</button>
</template>
<script setup>
const props = defineProps({
error: Object
})
const handleError = () => clearError({ redirect: '/' })
</script>
Error helper methods
→ 전역으로 발생하는 Nuxt 에러를 핸들링 할 수 있음
→ 이 함수는 client side, server side에서 middleware, plugin, setup함수에서 언제든지 사용 가능함. 이 함수는 트리거가 될때마다 에러페이지를 반환함. 이 에러페이지는 clearError 함수로 clear시킬 수 있음
→ 이 함수는 핸들링 된 Nuxt error를 clear하며 다른 페이지로 redirect 할 수 있음
Rendering errors within your app
<NuxtErrorBoundary> 컴포넌트는 다른 에러 페이지를 생성하지 않고도 에러페이지를 렌더링 하게 해줌.
이 컴포넌트는 반응형으로 에러를 핸들링 할 수 있음.
<template><!-- some content -->
<NuxtErrorBoundary @error="someErrorLogger">
<!-- You use the default slot to render your content -->
<template #error="{ error }">
You can display the error locally here.
<button @click="error = null">
This will clear the error.
</button>
</template>
</NuxtErrorBoundary>
</template>
Server Routes
Nuxt는 자동으로 /server/api, /server/routes, /server/middleware 디렉토리에 들어있는 파일들을 스캔하고 API와 서버 핸들러에 등록함
이 핸들러는 JSON 데이터를 리턴하며, Promise, event.res.end() 를 사용하여 응답을 보낸다.
ex) server/api/hello.ts 파일을
export default defineEventHandler((event) => {
return {
api: 'works'
}
})
전역적으로 await $fetch(’/api/hello’)로 API를 호출 할 수 있음
Server Routes
~/server/api 안의 파일들은 prefix로 /api라는 path를 자동으로 갖게 되며, /api라는 prefix가 붙지 않고 사용하려면 ~/server/routes 디렉토리에 추가 해야함.
예를들어 ~/server/routes/hello.ts 라는 파일을 아래와 같이 작성하면
export default defineEventHandler(() => 'Hello World!')
http://localhost:3000/hello 로 접근 할 수 있음
Server Middleware
Nuxt는 ~/server/middleware 디렉토리 안에 있는 모든 파일들을 자동으로 읽고 server middleware를 생성함.
middleware handler는 모든 요청에 server의 route를 거치기 전 헤더를 체크하거나 로직을 추가 하는데 사용 함.
ex) /server/middleware/log.ts
export default defineEventHandler((event) => {
console.log('New request: ' + event.req.url)
})
ex) /server/middleware/auth.ts
export default defineEventHandler((event) => {
event.context.auth = { user: 123 }
})
Matching Route Parameters
server routes는 동적으로 파라미터를 가져올 수 있다.
예를들어 /api/hello/[name].ts 파일의 이름을 event.context.params.name으로 찾아올 수 있음
ex) /server/api/hello/[name].ts
export default defineEventHandler(event => Hello, ${event.context.params.name}!)
await $fetch(’/api/hello/nuxt’)를 호출하면 Hello, nuxt!를 얻을 수 있음
Matching HTTP Method
파일 이름 뒤에 HTTP Method(.get, .post, .put, .delete ...)를 붙여서 핸들링 할 수 있음.
ex) /server/api/test.get.ts
export default defineEventHandler(() => 'Test get handler')
ex) /server/api/test.post.ts
export default defineEventHandler(() => 'Test post handler')
위의 예시들은 각각 get 핸들러 , post 핸들러를 리턴함
Catch-all route
Catch-all route는 루트 path 이후에 catch-all route가 정해져 있지 않으면 해당 path를 포함하는 모든 요청에 대한 핸들러를 작성 할 수 있음
ex) /server/api/foo/[...].ts
export default defineEventHandler(() => Default foo handler)
ex) /server/api/[...].ts
export default defineEventHandler(() => Default api handler)
Handling Requests with Body
ex) /server/api/submit.post.ts
export default defineEventHandler(async (event) => {
const body = await useBody(event)
return { body }
})
예시에서 $fetch(’/api/submit’, {method: ‘post’, body :{test:123}})로 호출 할 수 있음
.post.ts 파일은 useBody를 사용할 수 있음, 만약 .get.ts 파일을 핸들링 하려고 useBody를 쓰면 405 에러가 남
Handling Requests with query parameters
ex) /server/api/query.get.ts
export default defineEventHandler((event) => {
const query = useQuery(event)
return { a: query.param1, b: query.param2 }
})
Get access to the runtimeConfig
ex) /server/api/foo.ts
export default defineEventHandler((event) => {
const config = useRuntimeConfig()
return { key: config.KEY }
})
Access Request Cookies
ex)
export default defineEventHandler((event) => {
const cookies = useCookies(event)
return { cookies }
})
Runtime Config
Nuxt는 런타임에 config를 컨트롤 할 수 있는 config API를 제공함.
Exposing runtime config
nuxt.config파일의 runtimeConfig 옵션에 런타임 설정을 할 수 있음
ex) nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// The private keys which are only available within server-side
apiSecret: '123',
// Keys within public, will be also exposed to the client-side
public: {
apiBase: '/api'
}
}
})
Environment Variables
Nuxt CLI 는 built-in 으로 dotenv를 지원함
만약 .env 파일이 프로젝트 root 디렉토리에 존재하면 process.env에 자동으로 load 되고 module과 nuxt.config에서 접근 가능함
ex) .env
NUXT_API_SECRET=api_secret_token
NUXT_PUBLIC_API_BASE=https://nuxtjs.org
ex) nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
apiSecret: '',
public: {
apiBase: '', // Or a default value
}
},
})
Accessing runtime config
런타임 config에 접근 하기 위해 useRuntimeConfig() 를 호출함
ex)
<template><div><div>Check developer console!</div></div></template><script setup>
const config = useRuntimeConfig()
console.log('Runtime config:', config)
if (process.server) {
console.log('API secret:', config.apiSecret)
}
</script>
client side에서는 config.pubic만 접근 가능하며 읽기/쓰기가 가능함
server side에서는 모든 key에 접근 가능하지만, 쓰기가 불가능함
useRentimeConfig()는 setup이나 Lifecycle hook에서만 사용가능함
plugin에서 useRuntimeConfig를 사용하려면 defineNuxtPlugin 안에서 사용함
ex)
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig()
console.log('API base URL:', config.public.apiBase)
});
ex)
export default async () => {
const result = await $fetch('<https://my.api.com/test>', {
headers: {
Authorization: `Bearer ${useRuntimeConfig().apiSecret}`}
})
return result
}
Teleports
Vue3는 <Teleport> 컴포넌트를 제공함. 이는 Vue application 이 아닌 DOM의 어느곳에서든지 렌더링이 됨
Nuxt는 SSR에서 teleport는 body만 가능함, client-side에서 사용은 <ClientOnly> warpper로 감싸야 함
ex)
<template>
<button @click="open = true">
Open Modal
</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">
Close
</button>
</div>
</Teleport>
</template>
ex)
<ClientOnly>
<Teleport to="#some-selector">
<!-- content -->
</Teleport>
</ClientOnly>
</template>