Nuxt 3 핵심 SSR, 파일 기반 라우팅 및 컴포저블 공개
Min-jun Kim
Dev Intern · Leapcell

소개
빠르게 발전하는 프론트엔드 개발 환경에서 성능이 뛰어나고 유지 관리가 쉬우며 SEO 친화적인 웹 애플리케이션에 대한 수요는 매우 중요합니다. 단일 페이지 애플리케이션(SPA)은 풍부한 인터랙티브 사용자 경험을 제공하지만 초기 로드 시간과 검색 엔진 색인 생성에 종종 어려움을 겪습니다. 바로 이 지점에서 Nuxt 3와 같은 프레임워크가 등장하여 SPA의 가장 좋은 장점과 서버 사이드 렌더링(SSR)의 이점을 결합한 강력한 솔루션을 제공합니다. Nuxt 3는 초기 페이지 로드와 SEO를 개선할 뿐만 아니라 '설정보다 규칙'이라는 철학으로 개발을 간소화합니다. 이 글에서는 Nuxt 3 핵심의 세 가지 중요한 측면, 즉 정교한 서버 사이드 렌더링 기능, 우아한 파일 기반 라우팅 시스템, 그리고 매우 확장 가능한 컴포저블 API를 살펴보고 이러한 기능들이 어떻게 종합적으로 개발자가 뛰어난 웹 애플리케이션을 구축할 수 있도록 지원하는지 보여줄 것입니다.
Nuxt 3의 기반에 대한 심층 분석
Nuxt 3는 Vue 3, Vite 및 Nitro를 기반으로 구축되어 강력하고 매우 최적화된 개발 환경을 제공합니다. 핵심 메커니즘을 이해하는 것은 전체 잠재력을 활용하는 데 중요합니다.
서버 사이드 렌더링(SSR) 이해
서버 사이드 렌더링(SSR)은 웹 페이지의 초기 렌더링이 서버에서 발생하여 전체 HTML을 생성하고 클라이언트로 보내는 기술입니다. 이는 브라우저가 최소한의 HTML 셸을 받고 JavaScript를 사용하여 콘텐츠를 렌더링하는 클라이언트 사이드 렌더링(CSR)과는 상당히 다릅니다.
SSR이 중요한 이유
- 개선된 초기 로드 성능: 브라우저가 미리 렌더링된 HTML을 수신하므로 사용자는 더 빠르게 콘텐츠를 볼 수 있으며, "흰색 화면" 시간이 단축됩니다.
- 향상된 SEO(검색 엔진 최적화): 검색 엔진 크롤러는 완전히 렌더링된 HTML 콘텐츠를 쉽게 분석할 수 있어 더 나은 색인 생성 및 순위로 이어집니다.
- 느린 네트워크에서의 더 나은 사용자 경험: 인터넷 액세스가 제한된 사용자의 경우 SSR은 의미 있는 콘텐츠를 더 빨리 표시하는 것을 보장합니다.
Nuxt 3에서 SSR 구현 방법
Nuxt 3는 강력한 Nitro 서버 엔진을 통해 SSR을 구현합니다. 요청이 들어오면 Nitro는 이를 처리하고, 필요한 경우 데이터를 가져오고, Vue 구성 요소를 서버에서 HTML로 렌더링한 다음, 이 HTML과 필요한 JavaScript 및 CSS를 클라이언트로 보냅니다. HTML이 브라우저에 로드되면 Vue는 이를 "수화(hydrate)"합니다. 즉, 정적 HTML을 인수하여 상호 작용 가능하게 만들고, 사실상 완전히 기능하는 SPA로 변환합니다.
Nuxt 3의 useAsyncData
를 사용하여 데이터 가져오기와 함께 기본적인 SSR 예시를 살펴보겠습니다.
게시물 목록을 표시하는 페이지를 고려해 보세요. 페이지 로드 후 클라이언트 측에서 게시물을 가져오는 대신, useAsyncData
를 사용하여 서버에서 미리 가져올 수 있습니다.
<!-- pages/posts/index.vue --> <template> <div> <h1>Posts</h1> <p v-if="pending">Loading posts...</p> <div v-else> <ul v-if="posts && posts.length"> <li v-for="post in posts" :key="post.id"> {{ post.title }} </li> </ul> <p v-else>No posts found.</p> </div> <div v-if="error"> <p>Error loading posts: {{ error.message }}</p> </div> </div> </template> <script setup> const { data: posts, pending, error } = useAsyncData('posts', async () => { const response = await fetch('https://jsonplaceholder.typicode.com/posts'); if (!response.ok) { throw new new Error('Failed to fetch posts'); } return response.json(); }); </script>
이 예시에서 pages/posts/index.vue
에 액세스하면 서버의 Nuxt 3는 useAsyncData
훅을 실행합니다. jsonplaceholder.typicode.com
에서 게시물을 가져와 게시물 데이터로 HTML을 렌더링한 다음, 완전한 HTML을 브라우저로 보냅니다. 이를 통해 사용자는 클라이언트 측 JavaScript가 실행되고 데이터를 가져올 때까지 기다리지 않고 즉시 게시물 목록을 볼 수 있습니다.
파일 기반 라우팅으로 탐색 간소화
Nuxt 3는 직관적인 파일 기반 라우팅 시스템을 통해 라우팅을 대폭 간소화합니다. 이 접근 방식은 대부분의 경우 수동 라우팅 구성을 할 필요가 없도록 하며 "설정보다 규칙" 원칙을 준수합니다.
파일 기반 라우팅 작동 방식
Nuxt는 pages/
디렉터리 내의 파일 구조에 따라 자동으로 라우트를 생성합니다.
pages/index.vue
는/
라우트에 해당합니다.pages/about.vue
는/about
라우트에 해당합니다.pages/products/index.vue
는/products
라우트에 해당합니다.pages/products/[id].vue
는/products/:id
에 대한 동적 라우트를 생성하며, 여기서[id]
는 라우트 매개변수 역할을 합니다.pages/users/[[id]].vue
는/users
또는/users/:id
에 대한 선택적 동적 라우트를 생성합니다.
실제 예시
간단한 탐색 구조를 만들어 보겠습니다.
├── pages/
│ ├── index.vue
│ ├── about.vue
│ └── blog/
│ ├── index.vue
│ └── [slug].vue
이 구조는 다음과 같은 라우트를 자동으로 생성합니다.
/
(pages/index.vue
에서)/about
(pages/about.vue
에서)/blog
(pages/blog/index.vue
에서)/blog/:slug
(pages/blog/[slug].vue
에서)
이 페이지들 간을 탐색하려면 NuxtLink
컴포넌트를 사용할 수 있습니다.
<!-- layouts/default.vue (또는 모든 컴포넌트) --> <template> <nav> <NuxtLink to="/">Home</NuxtLink> <NuxtLink to="/about">About</NuxtLink> <NuxtLink to="/blog">Blog</NuxtLink> </nav> <slot /> </template>
그리고 동적 라우트의 경우 pages/blog/[slug].vue
에서:
<!-- pages/blog/[slug].vue --> <template> <div> <h1>Blog Post: {{ route.params.slug }}</h1> <!-- 특정 블로그 게시물의 콘텐츠 --> </div> </template> <script setup> import { useRoute } from 'vue-router'; // 또는 단순히 useNuxtApp().$route const route = useRoute(); // slug는 route.params.slug를 사용하여 액세스할 수 있습니다. </script>
이 시스템은 보일러플레이트 코드를 크게 줄여 라우트 관리를 직관적이고 오류 발생 가능성을 줄여줍니다.
컴포저블로 재사용성 향상
컴포저블은 Nuxt 3 아키텍처의 초석이며, Vue 3의 Composition API를 활용하여 코드 재사용성과 논리 구성을 개선합니다. 이는 반응형 상태 기반 논리를 캡슐화하고 여러 컴포넌트에서 추출하여 재사용할 수 있는 함수입니다.
컴포저블이란 무엇인가요?
컴포저블을 상태 관리, API 호출, 폼 처리 또는 애플리케이션의 여러 부분에서 반복될 수 있는 모든 기능 조각과 관련된 복잡한 논리를 추상화하는 특수 훅 또는 유틸리티 함수로 생각하십시오. 관례상 컴포저블은 composables/
디렉터리에 저장됩니다.
컴포저블 사용의 이점
- 개선된 코드 구성: 컴포넌트 논리를 깔끔하고 렌더링에 집중시킵니다.
- 향상된 재사용성: prop 드릴링이나 복잡한 이벤트 방출 없이 논리를 공유합니다.
- 더 나은 유지 관리성: 공유된 논리에 대한 변경은 한 곳에서만 하면 됩니다.
- 향상된 테스트 용이성: 격리된 논리는 독립적으로 테스트하기 더 쉽습니다.
간단한 카운터 컴포저블 예시
재사용 가능한 카운터 논리를 만들어 보겠습니다.
// composables/useCounter.js import { ref, computed } from 'vue'; export default function useCounter(initialValue = 0) { const count = ref(initialValue); const increment = () => { count.value++; }; const decrement = () => { count.value--; }; const doubleCount = computed(() => count.value * 2); return { count, increment, decrement, doubleCount, }; }
이제 이 컴포저블을 모든 Vue 컴포넌트 또는 Nuxt 페이지에서 사용할 수 있습니다.
<!-- pages/counter.vue --> <template> <div> <h1>Counter Page</h1> <p>Current Count: {{ count }}</p> <p>Double Count: {{ doubleCount }}</p> <button @click="increment">Increment</button> <button @click="decrement">Decrement</button> </div> </template> <script setup> import useCounter from '~/composables/useCounter'; // Nuxt는 ~/composables를 자동으로 별칭으로 지정합니다. const { count, increment, decrement, doubleCount } = useCounter(10); </script>
Nuxt는 composables/
디렉터리에서 컴포저블을 자동으로 가져와 최상위 수준에 정의된 경우 명시적인 import
문 없이도 전역적으로 사용할 수 있도록 합니다. 이는 사용을 더욱 단순화합니다.
컴포저블은 인증 상태 관리, 유효성 검사를 포함한 폼 제출 처리 또는 API에서 데이터 가져오기(자체적으로 Nuxt에서 제공하는 컴포저블인 useAsyncData
에서 본 것처럼)와 같은 보다 고급 시나리오에도 사용될 수 있습니다.
결론
Nuxt 3는 강력한 서버 사이드 렌더링 기능, 직관적인 파일 기반 라우팅 시스템, 그리고 매우 유연한 컴포저블 API로 인해 현대적인 웹 애플리케이션 구축을 위한 강력하고 우아한 프레임워크로 자리 잡고 있습니다. 이러한 핵심 요소는 개발자가 놀라운 효율성과 즐거운 개발 경험으로 성능이 뛰어나고 SEO 친화적이며 유지 관리가 가능한 애플리케이션을 구축할 수 있도록 종합적으로 지원합니다. Nuxt 3는 개발 워크플로우를 진정으로 최적화하여 광범위한 프로젝트에 훌륭한 선택이 됩니다.