Nuxt는 서로 다른 렌더링 모드인 유니버설 렌더링, 클라이언트 사이드 렌더링을 지원할 뿐만 아니라, 하이브리드 렌더링과 애플리케이션을 CDN 엣지 서버에서 렌더링할 수 있는 기능도 제공합니다.
브라우저와 서버 모두 JavaScript 코드를 해석하여 Vue.js 컴포넌트를 HTML 요소로 변환할 수 있습니다. 이 단계를 렌더링이라고 합니다. Nuxt는 유니버설 렌더링과 클라이언트 사이드 렌더링을 모두 지원합니다. 두 접근 방식은 각각 장단점이 있으며, 이에 대해 살펴보겠습니다.
기본적으로 Nuxt는 더 나은 사용자 경험, 성능, 검색 엔진 인덱싱 최적화를 위해 유니버설 렌더링을 사용하지만, 한 줄의 설정으로 렌더링 모드를 전환할 수 있습니다.
이 단계는 PHP나 Ruby 애플리케이션이 수행하는 전통적인 서버 사이드 렌더링과 유사합니다. 브라우저가 유니버설 렌더링이 활성화된 URL을 요청하면, Nuxt는 서버 환경에서 JavaScript(Vu e.js) 코드를 실행하고 완전히 렌더링된 HTML 페이지를 브라우저에 반환합니다. 또한 Nuxt는 페이지가 미리 생성된 경우 캐시에서 완전히 렌더링된 HTML 페이지를 반환할 수도 있습니다. 사용자는 클라이언트 사이드 렌더링과 달리 애플리케이션의 초기 콘텐츠 전체를 즉시 받게 됩니다.
HTML 문서가 다운로드되면 브라우저가 이를 해석하고, Vue.js가 문서의 제어권을 가져옵니다. 서버에서 한 번 실행되었던 동일한 JavaScript 코드가 이제 백그라운드에서 클라이언트(브라우저)에서 다시 실행되며, HTML에 리스너를 바인딩하여 상호작용을 가능하게 합니다(그래서 유니버설 렌더링이라고 부릅니다). 이를 **하이드레이션(Hydration)**이라고 합니다. 하이드레이션이 완료되면 페이지는 동적인 인터페이스와 페이지 전환과 같은 이점을 누릴 수 있습니다.
유니버설 렌더링을 사용하면 Nuxt 애플리케이션은 클라이언트 사이드 렌더링의 장점을 유지하면서도 빠른 페이지 로드 시간을 제공할 수 있습니다. 또한 콘텐츠가 이미 HTML 문서에 포함되어 있기 때문에 크롤러가 추가 작업 없이 이를 인덱싱할 수 있습니다.
무엇이 서버에서 렌더링되고, 무엇이 클라이언트에서 렌더링될까요?
유니버설 렌더링 모드에서 Vue 파일의 어떤 부분이 서버와/또는 클라이언트에서 실행되는지 궁금해하는 것은 자연스러운 일입니다.
<script setup lang="ts">
const counter = ref(0) // 서버와 클라이언트 환경 모두에서 실행됨
const handleClick = () => {
counter.value++ // 클라이언트 환경에서만 실행됨
}
</script>
<template>
<div>
<p>Count: {{ counter }}</p>
<button @click="handleClick">
Increment
</button>
</div>
</template>
초기 요청 시, counter ref는 <p> 태그 안에서 렌더링되기 때문에 서버에서 초기화됩니다. 이때 handleClick의 내용은 전혀 실행되지 않습니다. 브라우저에서의 하이드레이션 동안 counter ref가 다시 초기화됩니다. 이후 handleClick이 버튼에 바인딩되며, 따라서 handleClick의 본문은 항상 브라우저 환경에서 실행된다고 추론하는 것이 합리적입니다.
미들웨어와 페이지는 서버에서 실행되며, 하이드레이션 동안 클라이언트에서도 실행됩니다. 플러그인은 서버, 클라이언트 또는 둘 다에서 렌더링될 수 있습니다. 컴포넌트는 클라이언트에서만 실행되도록 강제할 수도 있습니다. 컴포저블과 유틸리티는 사용되는 컨텍스트에 따라 렌더링됩니다.
서버 사이드 렌더링의 장점:
서버 사이드 렌더링의 단점:
유니버설 렌더링은 매우 다재다능하여 거의 모든 사용 사례에 적합하며, 특히 콘텐츠 중심 웹사이트에 잘 어울립니다: 블로그, 마케팅 웹사이트, 포트폴리오, 이커머스 사이트, 마켓플레이스 등.
기본적으로 전통적인 Vue.js 애플리케이션은 브라우저(또는 클라이언트)에서 렌더링됩니다. 그런 다음 브라우저가 현재 인터페이스를 생성하기 위한 명령을 포함한 모든 JavaScript 코드를 다운로드하고 파싱한 후, Vue.js가 HTML 요소를 생성합니다.
클라이언트 사이드 렌더링의 장점:
window 객체와 같은 브라우저 전용 API를 사용하는 등, 코드의 서버 호환성에 대해 걱정할 필요가 없습니다.클라이언트 사이드 렌더링의 단점:
클라이언트 사이드 렌더링은 인덱싱이 필요 없거나 사용자가 자주 방문하는 고도로 인터랙티브한 웹 애플리케이션에 적합한 선택입니다. 브라우저 캐싱을 활용하여 이후 방문 시 다운로드 단계를 건너뛸 수 있으며, 예를 들어 SaaS, 백오피스 애플리케이션, 온라인 게임 등이 이에 해당합니다.
Nuxt에서 nuxt.config.ts에 다음과 같이 설정하여 클라이언트 사이드 전용 렌더링을 활성화할 수 있습니다:
export default defineNuxtConfig({
ssr: false,
})
ssr: false를 사용하는 경우, 앱이 하이드레이션될 때까지 렌더링할 로딩 화면용 HTML을 ~/spa-loading-template.html에 배치해야 합니다.nuxt generate 또는 nuxt build --prerender 명령으로 앱을 정적 호스팅에 배포하는 경우, 기본적으로 Nuxt는 각 페이지를 개별 정적 HTML 파일로 렌더링합니다.
nuxt generate 또는 nuxt build --prerender 명령으로 앱을 프리렌더링하면, 출력 폴더에 서버가 포함되지 않기 때문에 어떤 서버 엔드포인트도 사용할 수 없습니다. 서버 기능이 필요하다면 대신 nuxt build를 사용하세요.순수 클라이언트 사이드 렌더링만 사용하는 경우라면, 이는 불필요할 수 있습니다. 모든 요청에 대해 정적 웹 호스트가 제공하도록 설정할 수 있는 단일 index.html 파일과 200.html, 404.html 폴백만 필요할 수도 있습니다.
이를 위해서는 라우트가 프리렌더링되는 방식을 변경하면 됩니다. nuxt.config.ts의 hooks에 다음을 추가하세요:
export default defineNuxtConfig({
hooks: {
'prerender:routes' ({ routes }) {
routes.clear() // 기본값을 제외한 어떤 라우트도 생성하지 않음
},
},
})
이렇게 하면 다음 세 개의 파일이 생성됩니다:
index.html200.html404.html200.html과 404.html은 사용 중인 호스팅 제공업체에서 유용하게 사용될 수 있습니다.
클라이언트 렌더링 앱을 프리렌더링할 때, Nuxt는 기본적으로 index.html, 200.html, 404.html 파일을 생성합니다. 그러나 빌드에서 이 파일들 중 일부(또는 전부)가 생성되지 않도록 해야 하는 경우, Nitro의 'prerender:generate' 훅을 사용할 수 있습니다.
export default defineNuxtConfig({
ssr: false,
nitro: {
hooks: {
'prerender:generate' (route) {
const routesToSkip = ['/index.html', '/200.html', '/404.html']
if (routesToSkip.includes(route.route)) {
route.skip = true
}
},
},
},
})
하이브리드 렌더링은 **Route Rules(라우트 규칙)**을 사용하여 라우트별로 서로 다른 캐싱 규칙을 적용하고, 특정 URL에 대한 새 요청에 서버가 어떻게 응답해야 할지 결정할 수 있게 해줍니다.
이전에는 Nuxt 애플리케이션과 서버의 모든 라우트/페이지가 유니버설 또는 클라이언트 사이드 중 하나의 동일한 렌더링 모드를 사용해야 했습니다. 하지만 여러 경우에 일부 페이지는 빌드 시점에 생성될 수 있는 반면, 다른 페이지는 클라이언트 사이드 렌더링이 필요할 수 있습니다. 예를 들어, 관리자 섹션이 있는 콘텐츠 웹사이트를 생각해 보세요. 모든 콘텐츠 페이지는 기본적으로 정적이며 한 번만 생성되면 되지만, 관리자 섹션은 로그인 기능이 필요하고 더 동적인 애플리케이션처럼 동작해야 합니다.
Nuxt는 라우트 규칙과 하이브리드 렌더링을 지원합니다. 라우트 규칙을 사용하면 특정 Nuxt 라우트 그룹에 대한 규칙을 정의하고, 렌더링 모드를 변경하거나 라우트에 따라 캐시 전략을 지정할 수 있습니다!
Nuxt 서버는 Nitro 캐싱 레이어를 사용하여 해당 미들웨어를 자동으로 등록하고, 라우트를 캐시 핸들러로 감쌉니다.
export default defineNuxtConfig({
routeRules: {
// 홈페이지는 빌드 시점에 프리렌더링
'/': { prerender: true },
// 상품 페이지는 요청 시 생성, 백그라운드에서 재검증, API 응답이 변경될 때까지 캐시
'/products': { swr: true },
// 개별 상품 페이지는 요청 시 생성, 백그라운드에서 재검증, 1시간(3600초) 동안 캐시
'/products/**': { swr: 3600 },
// 블로그 목록 페이지는 요청 시 생성, 백그라운드에서 재검증, CDN에서 1시간(3600초) 동안 캐시
'/blog': { isr: 3600 },
// 개별 블로그 글 페이지는 다음 배포까지 한 번만 요청 시 생성되어 CDN에 캐시
'/blog/**': { isr: true },
// 관리자 대시보드는 클라이언트 사이드에서만 렌더링
'/admin/**': { ssr: false },
// API 라우트에 cors 헤더 추가
'/api/**': { cors: true },
// 레거시 URL 리다이렉트
'/old-page': { redirect: '/new-page' },
},
})
사용할 수 있는 다양한 속성은 다음과 같습니다:
redirect: string - 서버 사이드 리다이렉트를 정의합니다.ssr: boolean - 앱의 일부 섹션에 대해 HTML 서버 사이드 렌더링을 비활성화하고, ssr: false로 브라우저에서만 렌더링되도록 합니다.cors: boolean - cors: true로 cors 헤더를 자동으로 추가합니다. headers로 출력 값을 커스터마이즈할 수 있습니다.headers: object - 사이트의 특정 섹션(예: 에셋)에 특정 헤더를 추가합니다.swr: number | boolean - 서버 응답에 캐시 헤더를 추가하고, 서버 또는 리버스 프록시에 구성 가능한 TTL(time to live) 동안 캐시합니다. Nitro의 node-server 프리셋은 전체 응답을 캐시할 수 있습니다. TTL이 만료되면, 캐시된 응답이 전송되는 동시에 백그라운드에서 페이지가 재생성됩니다. true를 사용하면 MaxAge 없이 stale-while-revalidate 헤더가 추가됩니다.isr: number | boolean - 동작은 swr와 동일하지만, 이를 지원하는 플랫폼(현재 Netlify 또는 Vercel)에서는 응답을 CDN 캐시에 추가할 수 있습니다. true를 사용하면, 콘텐츠는 다음 배포까지 CDN 내부에 유지됩니다.prerender: boolean - 빌드 시점에 라우트를 프리렌더링하고, 이를 정적 에셋으로 빌드에 포함합니다.noScripts: boolean - 사이트의 일부 섹션에 대해 Nuxt 스크립트와 JS 리소스 힌트 렌더링을 비활성화합니다.appMiddleware: string | string[] | Record<string, boolean> - 애플리케이션의 Vue 앱 부분(즉, Nitro 라우트가 아닌) 내에서 페이지 경로에 대해 어떤 미들웨어를 실행하거나 실행하지 않을지 정의할 수 있습니다.가능한 경우, 라우트 규칙은 최적의 성능을 위해 배포 플랫폼의 네이티브 규칙(Netlify와 Vercel이 현재 지원됨)에 자동으로 매핑됩니다.
nuxt generate를 사용할 때는 사용할 수 없습니다.예시:
엣지 사이드 렌더링(ESR)은 Nuxt에 도입된 강력한 기능으로, 콘텐츠 전송 네트워크(CDN)의 엣지 서버를 통해 사용자와 더 가까운 곳에서 Nuxt 애플리케이션을 렌더링할 수 있게 해줍니다. ESR을 활용하면 성능과 지연 시간을 개선하여 향상된 사용자 경험을 제공할 수 있습니다.
ESR에서는 렌더링 프로세스가 네트워크의 '엣지', 즉 CDN의 엣지 서버로 이동합니다. ESR은 실제 렌더링 모드라기보다는 배포 대상에 더 가깝다는 점에 유의하세요.
페이지에 대한 요청이 발생하면, 원본 서버까지 이동하는 대신 가장 가까운 엣지 서버에서 요청을 가로챕니다. 이 서버가 페이지의 HTML을 생성하여 사용자에게 다시 전송합니다. 이 과정은 데이터가 이동해야 하는 물리적 거리를 최소화하여 지연 시간을 줄이고 페이지를 더 빠르게 로드합니다.
엣지 사이드 렌더링은 Nuxt를 구동하는 서버 엔진인 Nitro 덕분에 가능합니다. Nitro는 Node.js, Deno, Cloudflare Workers 등 다양한 플랫폼을 지원합니다.
현재 ESR을 활용할 수 있는 플랫폼은 다음과 같습니다:
nuxt build 명령과 Git 연동을 사용하여 제로 설정으로 사용할 수 있는 Cloudflare Pagesnuxt build 명령과 NITRO_PRESET=vercel-edge 환경 변수를 사용하는 Vercel Cloudnuxt build 명령과 NITRO_PRESET=netlify-edge 환경 변수를 사용하는 Netlify Edge Functions하이브리드 렌더링은 라우트 규칙과 함께 엣지 사이드 렌더링을 사용할 때도 활용할 수 있습니다.