# Nuxt 3: SSR과 정적 생성 완벽 가이드
> Nuxt 3로 SSR과 정적 생성을 마스터하십시오. useFetch부터 route rules까지, Vue.js 애플리케이션의 성능을 최적화하는 방법을 배웁니다.
- Published: 2026-02-11
- Updated: 2026-04-28
- Author: SharpSkill
- Tags: nuxt 3, vue.js, ssr, 정적 생성, 웹 성능
- Reading time: 12 min
---
Nuxt 3은 다양한 사용 사례에 적합한 여러 렌더링 모드를 제공함으로써 Vue.js 애플리케이션을 구축하는 방식을 바꿔놓습니다. Server-Side Rendering(SSR)부터 정적 생성, 하이브리드 렌더링까지 이 프레임워크는 성능과 SEO를 최적화하기 위한 뛰어난 유연성을 제공합니다.
> **사전 요구 사항**
>
> 이 튜토리얼은 Vue 3과 Composition API에 대한 기본 지식을 전제로 합니다. 서버 사이드 렌더링 개념에 익숙하면 도움이 되지만 필수는 아니며, 기본 개념은 가이드 전반에 걸쳐 설명합니다.
## Nuxt 3의 렌더링 모드 이해하기
코드를 살펴보기 전에 사용할 수 있는 렌더링 모드 간의 차이를 이해하는 것이 매우 중요합니다. 각 모드는 성능, SEO, 사용자 경험 측면에서 특정한 요구를 충족합니다.
SSR(Server-Side Rendering)은 모든 요청에 대해 서버에서 HTML을 생성합니다. 정적 생성(SSG)은 빌드 시점에 모든 페이지를 미리 생성합니다. 하이브리드 모드는 페이지 단위로 이러한 접근 방식을 조합할 수 있게 해줍니다.
```typescript
// nuxt.config.ts
// 다양한 렌더링 모드의 설정
export default defineNuxtConfig({
// SSR 기본 활성화 (SEO를 위해 권장)
ssr: true,
// 정적 생성: 모든 페이지를 사전 렌더링
// 빌드에는 'npm run generate' 사용
// target: 'static', // Nuxt 2 문법
// 하이브리드 모드: 라우트 단위로 설정 가능
routeRules: {
// 메인 페이지: 사전 렌더링 및 캐시
'/': { prerender: true },
// 블로그: 정적 생성
'/blog/**': { prerender: true },
// 대시보드: 클라이언트 사이드 렌더링만 사용
'/dashboard/**': { ssr: false },
// API: 사전 렌더링 없음
'/api/**': { prerender: false }
}
})
```
이 설정은 하이브리드 모드의 강력함을 보여줍니다. 애플리케이션의 각 영역이 자신의 요구에 가장 적합한 렌더링 모드를 사용합니다.
## useFetch와 useAsyncData를 활용한 데이터 가져오기
Nuxt 3은 데이터를 동형(isomorphic)으로 가져오기 위한 두 가지 주요 컴포저블을 제공합니다. 이 컴포저블들은 서버 측과 클라이언트 측 모두에서 동작하며 하이드레이션을 자동으로 관리합니다.
`useFetch`는 HTTP 호출을 단순화하는 `useAsyncData`의 래퍼입니다. `useAsyncData`는 고급 사용 사례에서 더 많은 제어를 제공합니다.
```vue
기사를 불러오는 중...
{{ article.title }}
```
더 많은 제어가 필요한 경우 `useAsyncData`를 사용하면 임의의 비동기 함수를 실행할 수 있습니다.
```vue
```
이 컴포저블들은 이중 페칭을 방지합니다. 서버에서 가져온 데이터는 HTML 페이로드에 직렬화되어 클라이언트 하이드레이션 시 재사용됩니다.
## 서버 훅으로 SSR 커스터마이즈하기
Nuxt 3의 SSR은 서버 훅을 통해 커스터마이즈할 수 있습니다. 이러한 훅은 렌더링 사이클의 다양한 단계에 개입하여 기본 동작을 수정할 수 있게 해줍니다.
```typescript
// server/plugins/render-hooks.ts
// SSR 렌더링을 커스터마이즈하기 위한 서버 플러그인
export default defineNitroPlugin((nitroApp) => {
// 각 페이지 렌더링 전에 실행되는 훅
nitroApp.hooks.hook('render:html', (html, { event }) => {
// 스크립트나 메타데이터 주입
html.head.push(`
`)
})
// 렌더링 캐시 관리를 위한 훅
nitroApp.hooks.hook('render:response', (response, { event }) => {
// 사용자 정의 캐시 헤더 추가
const path = event.path
if (path.startsWith('/blog/')) {
// 블로그 기사에는 긴 캐시
response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
} else if (path.startsWith('/api/')) {
// API에는 캐시 사용 안 함
response.headers['Cache-Control'] = 'no-store'
}
})
})
```
> **SSR 성능**
>
> `render:response` 훅은 HTTP 캐시 전략을 구현하기에 이상적입니다. SSR을 Cache-Control 헤더를 존중하는 CDN과 결합하면 사전 렌더링된 페이지를 제공하면서 무효화 능력을 유지할 수 있습니다.
## nuxt generate를 활용한 정적 생성
정적 생성은 빌드 시점에 모든 페이지를 미리 빌드합니다. 이 접근 방식은 블로그, 문서, 마케팅 사이트와 같이 콘텐츠가 안정적인 사이트에 이상적입니다.
동적 라우트의 경우 Nuxt가 생성해야 하는 모든 URL을 알아야 합니다. `prerender:routes` 훅을 통해 이러한 라우트를 프로그래밍 방식으로 정의할 수 있습니다.
```typescript
// nuxt.config.ts
// 정적 생성을 위한 완전한 설정
export default defineNuxtConfig({
// 정적 생성 활성화
nitro: {
prerender: {
// 자동 링크 크롤링 활성화
crawlLinks: true,
// 항상 포함할 라우트
routes: ['/', '/about', '/contact'],
// 일부 라우트 무시
ignore: ['/admin', '/api']
}
},
hooks: {
// 동적 라우트를 생성하기 위한 훅
async 'prerender:routes'(ctx) {
// API 또는 DB에서 기사 가져오기
const articles = await fetch('https://api.example.com/articles')
.then(res => res.json())
// 기사 라우트 추가
for (const article of articles) {
ctx.routes.add(`/blog/${article.slug}`)
}
// 카테고리 가져오기
const categories = await fetch('https://api.example.com/categories')
.then(res => res.json())
for (const category of categories) {
ctx.routes.add(`/category/${category.slug}`)
}
}
}
})
```
페이지 수가 많은 프로젝트의 경우 자동 크롤러로는 부족할 수 있습니다. 별도의 설정 파일을 사용하는 더 견고한 접근 방식을 소개합니다.
```typescript
// server/utils/generate-routes.ts
// 동적 라우트 목록을 생성하는 유틸리티
import { prisma } from './prisma'
export async function getAllStaticRoutes(): Promise {
const routes: string[] = []
// 블로그 기사
const articles = await prisma.article.findMany({
where: { published: true },
select: { slug: true, category: { select: { slug: true } } }
})
for (const article of articles) {
routes.push(`/blog/${article.category.slug}/${article.slug}`)
}
// 상품 페이지
const products = await prisma.product.findMany({
where: { active: true },
select: { slug: true }
})
for (const product of products) {
routes.push(`/products/${product.slug}`)
}
// 태그 페이지
const tags = await prisma.tag.findMany({
select: { slug: true }
})
for (const tag of tags) {
routes.push(`/tags/${tag.slug}`)
}
return routes
}
```
## routeRules로 하이브리드 렌더링하기
하이브리드 렌더링은 Nuxt 3의 대표 기능입니다. 라우트마다 서로 다른 렌더링 규칙을 정의해 SSR과 SSG의 장점을 결합할 수 있게 해줍니다.
```typescript
// nuxt.config.ts
// 하이브리드 렌더링의 고급 설정
export default defineNuxtConfig({
routeRules: {
// 마케팅 페이지: 사전 렌더링하고 장기간 캐시
'/': { prerender: true },
'/pricing': { prerender: true },
'/features/**': { prerender: true },
// 블로그: ISR (Incremental Static Regeneration)
// 매시간 재검증
'/blog/**': {
isr: 3600,
prerender: true
},
// 문서: 재검증을 포함한 CDN 캐시
'/docs/**': {
swr: 86400, // Stale-while-revalidate
prerender: true
},
// 이커머스: 짧은 캐시를 가진 SSR
'/products/**': {
ssr: true,
cache: {
maxAge: 60,
staleMaxAge: 300
}
},
// 장바구니와 결제: 클라이언트 사이드 전용
'/cart': { ssr: false },
'/checkout/**': { ssr: false },
// 대시보드: SPA 모드
'/dashboard/**': {
ssr: false,
// 사전 렌더링 비활성화
prerender: false
},
// API 라우트: 기본적으로 캐시 없음
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
}
}
}
})
```
이 설정은 일반적인 현대 애플리케이션 아키텍처를 보여줍니다. 공개 페이지는 SSG로 SEO에 최적화되고, 인터랙티브한 영역은 클라이언트 사이드 렌더링을 사용합니다.
## 데이터 캐싱을 통한 성능 최적화
페이지 캐싱뿐 아니라 Nuxt 3은 가져온 데이터를 캐싱할 수 있게 해줍니다. 이 전략은 API 부하를 줄이고 응답 시간을 개선합니다.
```typescript
// server/api/articles/[slug].get.ts
// 데이터 캐싱이 적용된 API 엔드포인트
import { getArticleBySlug } from '~/server/utils/articles'
export default defineCachedEventHandler(
async (event) => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({
statusCode: 400,
message: 'slug가 없습니다'
})
}
const article = await getArticleBySlug(slug)
if (!article) {
throw createError({
statusCode: 404,
message: '기사를 찾을 수 없습니다'
})
}
return article
},
{
// slug 기반 캐시 키
getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
// 캐시 유지 시간: 1시간
maxAge: 3600,
// Stale-while-revalidate: 업데이트 중에도 오래된 캐시 제공
staleMaxAge: 7200,
// 태그 기반 무효화
tags: ['articles']
}
)
```
콘텐츠가 변경될 때 캐시를 무효화하기 위해 Nuxt는 태그 시스템을 제공합니다.
```typescript
// server/api/articles/[slug].put.ts
// 캐시 무효화를 동반한 기사 업데이트
import { updateArticle } from '~/server/utils/articles'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
const body = await readBody(event)
// 기사 업데이트
const article = await updateArticle(slug, body)
// 이 기사에 대한 캐시 무효화
await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)
// 또는 태그 기반 무효화 (모든 기사)
// await useStorage('cache').clear('articles')
return article
})
```
> **분산 캐시**
>
> 다중 인스턴스 환경의 프로덕션에서는 인메모리 캐시만으로는 충분하지 않습니다. 인스턴스 간 일관성을 보장하기 위해 Nitro 설정을 통해 Redis 또는 다른 분산 시스템을 구성하는 것을 권장합니다.
## SEO와 메타데이터 관리
SSR은 메타데이터를 서버 측에서 생성함으로써 SEO를 최적화할 수 있게 해줍니다. Nuxt 3은 메타 태그를 동적으로 관리하기 위한 여러 가지 접근 방식을 제공합니다.
```vue
```
정적 페이지의 경우 메타데이터를 컴포넌트에서 직접 정의할 수 있습니다.
```vue
```
## 배포와 프로덕션 고려 사항
배포 선택은 사용하는 렌더링 모드에 따라 달라집니다. 주요 옵션과 그 설정은 다음과 같습니다.
```typescript
// nuxt.config.ts
// 다양한 배포 환경을 위한 설정
export default defineNuxtConfig({
nitro: {
// 대상 플랫폼에 맞춘 프리셋
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare
// preset: 'node-server', // 일반적인 Node.js
// 프로덕션 환경의 Node.js를 위한 설정
preset: 'node-server',
// 응답 압축
compressPublicAssets: true,
// 캐시 저장소 설정
storage: {
cache: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
},
// 런타임 환경 변수
runtimeConfig: {
// 비밀 값(클라이언트에 노출되지 않음)
apiSecret: process.env.API_SECRET,
// 공개 설정
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})
```
정적 배포의 경우 `npm run generate` 명령은 어떤 정적 파일 호스트에도 배포할 준비가 된 `.output/public` 폴더를 생성합니다.
```bash
# 정적 생성
npm run generate
# .output/public의 내용은 다음 위치에 배포할 수 있습니다:
# - Vercel(자동 감지)
# - Netlify(자동 설정)
# - GitHub Pages
# - S3 + CloudFront
# - 임의의 CDN 또는 정적 파일 서버
```
## 결론
Nuxt 3은 Vue.js 애플리케이션을 렌더링하기 위한 탁월한 유연성을 제공합니다. SSR, SSG, 하이브리드 렌더링 중 무엇을 선택할지는 각 프로젝트의 구체적인 요구 사항에 달려 있습니다.
**주요 포인트:**
✅ **SSR**: 좋은 SEO가 필요한 동적 콘텐츠에 이상적(이커머스, 뉴스 사이트)
✅ **SSG**: 안정적인 콘텐츠에 적합(블로그, 문서, 마케팅 사이트)
✅ **하이브리드**: 다양한 요구를 가진 복잡한 애플리케이션에 가장 적합한 접근 방식
✅ **useFetch/useAsyncData**: 자동 하이드레이션과 캐시 관리
✅ **routeRules**: 각 라우트의 동작에 대한 세밀한 설정
✅ **캐싱**: 프로덕션 성능을 최적화하기 위한 다양한 전략
하이브리드 렌더링과 잘 설계된 캐싱 전략을 결합하면 SEO에 최적화된 고성능 애플리케이션을 구축하면서도 Single Page Applications의 인터랙티브함을 유지할 수 있습니다.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/ko/blog/vue-nuxt/nuxt-3-ssr-static-generation