Nuxt란?

Nuxt는 Vue.js로 빠르게 웹을 제작할 수 있게 도와주는 프레임워크이다.
웹 어플리케이션을 제작할 때 필요한 뷰엑스, 라우터, axios와 같은 라이브러리들을 미리 구성하여 싱글 페이지 어플리케이션(Single Page Application), 서버 사이드 렌더링(Server Side Rendering), 정적 웹 사이트(Static Generated Website)를 쉽게 제작할 수 있다.

서버 사이드 렌더링이란?

서버 사이드 렌더링이란 웹 페이지의 내용을 서버에서 모두 작성해서 클라이언트(브라우저)로 보낸 뒤 화면에 그리는 방식을 의미한다.

Nuxt의 장점

  • 검색 엔진 초기화
  • 초기 프로젝트 설정 비용 감소와 생상선 향상
    • ESLint, Prettier
    • 라우터, 스토어 등의 라이브러리 설치 및 설정 파일 필요 X
    • 파일 기반의 라우팅 방식, 설정 파일 자동 생성
  • 페이지 로딩 속도와 사용자 경험 향상
    • 브라우저가 하는 일을 서버가 나눠준다
    • 모르면 지나칠 수 있는 코드 스플리팅이 기본으로 설정

Nuxt 특징

  • 서버 사이드 렌더링
  • 규격화된 폴더 구조(layout, store, middleware, plugins 등)
  • pages 폴더 기반의 자동 라우팅 생성
  • 코드 스플리팅
  • 비동기 데이터 요청 속성
  • ES6/ES6+ 변환
  • 웹팩을 비롯한 기타 설정

Nuxt 시작하기

Nuxt 프로젝트를 구성하기 위해 다음 명령어를 실행한다.

npm init nuxt-app 프로젝트명

프로젝트가 생성되고 나면 명령어 실행 창을 참고하여 명령어를 실행한다.

cd 프로젝트명
npm run dev

명령어 실행 창에 안내된 주소를 브라우저에 입력하고 결과를 확인한다.

서버 사이드 렌더링이란?

서버 사이드 렌더링이란 서버에서 페이지를 그려 클라이언트(브라우저)로 보낸 후 화면에 표시하는 기법을 의미한다.
뷰 싱글 페이지 애플리케이션을 서버 사이드 렌더링의 반대인 클라이언트 사이드 렌더링 방식이다.
이 글에서는 클라이언트 사이트 렌더링과 서버 사이드 렌더링 방식의 차이점을 살펴보고 서버 사이드 렌더링의 장단점을 분석해 보겠다.

클라이언트 사이드 렌더링

클라이언트 사이드 렌더링을 이해하기 위해서 뷰 CLI로 생성된 프로젝트의 실행 결과를 살펴보겠다.
아래는 뷰 CLI로 생성한 프로젝트의 기본 코드이다.

// src/main.js
import Vue from "vue";
import App from "./App.vue";

new Vue({
  render: (h) => h(App),
}).$mount("#app");

위 코드는 뷰의 인스턴스를 생성하는 코드이다.
이 인스턴스는 아래 index.html 파일의 app 아이디를 갖는 태그에 부착된다.

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="">
  <head>
    <!-- ... -->
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

CLI로 프로젝트를 생성하고 난 후 npm run serve로 프로젝트를 실행하고 브라우저를 확인하면 뷰 기본 페이지가 뜬다.
이 때 개발자 패널의 Network 탭에서 Doc으로 필터링한 결과는 아래와 같다.

서버에서 넘겨받은 HTML 코드에는 body 태그 본문에 <div id="app"></div> 밖에 없지만 화면에는 Welcome To Your Vue.js App 텍스트와 이미지가 있다.
이 텍스트와 이미지는 모두 클라이언트(브라우저)에서 동작한 Vue.js 라이브러리가 그려준 것이다.
즉 브라우저에서 화면의 결과를 그려낸 것이다.

서버 사이드 렌더링과 클라이언트 렌더링 차이점

그럼 서버 사이드의 렌더링은 앞에서 살펴본 클라이언트 사이드 렌더링과 차이점은 어디서 화면에 보일 페이지의 내용을 그리느냐의 차이이다.
클라이언트 사이드 렌더링은 페이지의 내용을 브라우저에서 그리고 서버 사이드 렌더링은 서버에서 페이지의 내용을 다 그려서 브라우저로 던져준다.

서버 사이드 렌더링은 왜 쓸까?

서버 사이드 렌더링을 쓰는 목적은 크게 "검색 엔진 최적화"와 "빠른 페이지 렌더링"이다.
검색 엔진 최적화란 구글, 네이버와 같은 검색 사이트에서 검색했을 때 결과가 사용자에게 많이 노출될 수 있도록 최적화 하는 기법이다.
특히, SNS에서 링크를 공유했을 때 해당 웹 사이트의 정보를 이미지와 설명으로 표시해주는 OG(Open Graph) Tag를 페이지 별로 적용하기 위해서는 서버 사이드 렌더링이 효율적이다.

또한, 서버 사이드 렌더링은 빈 HTML 페이지를 받아 브라우저에서 그리는 클라이언트 사이드 렌더링과 다르게 서버에서 미리 그려서 브라우저로 보내주기 때문에 페이지를 그리는 시간을 단축할 수 있다.
사용자 입장에서는 화면에 유의미한 정보가 표시되는 시간이 빨라지는 것이다.

서버 사이드 렌더링의 단점

이렇게만 보면 서버 사이드 렌더링을 하는게 좋겠네 라고 생각할 수 있지만 시작하기 전에 주의해야 할 점이 있다.
서버 사이드 렌더링은 Node.js 웹 애플리케이션 실행 방법을 알아야하고 서버쪽 환경 구성과 함께 클라이언트, 서버 빌드에 대한 이해가 필요하다.
따라서, 프론트엔드 개발 입문자 입장에서는 쉽지 않은 진입 장벽이 존재한다.

또한, Node.js 환경에서 실행ㄹ되기 때문에 브라우저 관련 API를 다룰 때 주의해야 한다.
뷰 싱글 페이지 애플리케이션의 라이프 사이클 훅과는 다른 환경(브라우저가 아닌 Node.js)에서 동작하기 때문에 beforeCreatecreated에서 windowdocument와 같은 브라우저 객체에 접근할 수 없다.

서버 사이드 렌더링의 경우 컴포넌트가 최초로 생성되는 시점에 브라우저 위가 아니라 Node.js 환경이기 때문에 beforecreatecreated에서 브라우저 객체를 접근할 수 없다.
대신 beforeMountmounted에서 windowdocument를 접근할 수 있다.

유니버설(Universal) 모드란?

Nuxt의 렌더링 모드 중 하나인 유니버설 모드의 개념과 동작 원리에 대해서 설명하겠다.

Nuxt 모드

Nuxt를 처음 설치할 때 다음의 두 가지 렌더링 모드를 선택할 수 있다.

  • univeral
  • Single Page App

공식문서에서는 유니버설 모드를 서버 사이드 렌더링과 클라이언트 사이드 네비게이션(Client Side Navigation)의 조합으로 설명하고 있다.
클라이언트 사이드 네비게이션이란 브라우저에서 페이지 간 이동시 클라이언트 쪽 자바스크립트 라우팅으로 이동하는 방식을 의미한다.
웹 서비스 페이지를 최초로 접근할 때는 서버 사이드 렌더링 방식으로 화면을 그려주지만 이후에 해당 서비스 내의 다른 페이지로 이동할 때는 자바스크립트 라우팅 방식을 사용하는 것이 유니버설 모드이다.

싱글 페이지 애플리케이션은 기존의 Vue가 제공하던 방식과 동일하니 본문에서 다루지 않겠다.

유니버설 모드의 등장

SSR은 기존의 싱글 페이지 애플리케이션이 가지고 있는 검색 엔진 최적화(Search Engine Optimization) 문제와 페이지 렌더링 속도 문제를 서버 사이드 렌더링이 해결할 수 있다는 것이 요점이다.

하지만 웹의 역사를 보면 서버 사이드 렌더링은 새로운 개념이 아니다.
대중적인 싱글 페이지 애플리케이션이 등장하기 전까지는 서버 사이드 렌더링을 기반으로 멀티 페이지 애플리케이션이 대세였다.
다만, 화면 전체 또는 일부를 변경할 때마다 서버 측 렌더링이 완료될 때까지 기다리기 때문에 깜빡임이 발생할 수 밖에 없었고, 결국 깜빡임이 없는 클라이언트 사이드 렌더링 기반의 싱글 페이지 애플리케이션에 밀려나게 되었다.

그렇다면 우리는 SEO를 포기하고 다시 클라이언트 사이드 렌더링으로 회귀해야던가 사용자 경험을 포기하고 서버 사이드 렌더링에 고집을 해야하는데 둘의 장단점은 분명하기 때문에 한쪽만 선택하면 반드시 기회비용이 발생한다.

그래서 Nuxt는 서버 사이드 렌더링과 클라이언트 렌더링을 모두 선택하기로 했다.
Nuxt 앱은 첫 방문할 때는 서버에서 그리고 이후 페이지 간 이동은 클라이언트 사이드에서 그리기로 했다.
이를 위해서 Nuxt는 클라이언트 사이드 하이드레이션(Client Side Hydration)과 코드 분할(Code Splitting), 프리패칭(Prefetching)을 활용하고 있다.

클라이언트 사이드 하이드레이션

하이드레이션은 간단하게 말하면 서버로부터 받은 정적 HTML을 사용자와 상호작용할 수 있는 다이나믹 DOM으로 바꾸는 방법이다.
유니버설 모드에서 Nuxt 앱에 사용자가 최초 접근을 하면 서버로부터 정적 HTML을 받는 동시에 HTML을 다이나믹 DOM으로 바꿔줄 자바스크립트 번들을 함께 받아온다.
브라우저는 받아온 자바스크립트 번들을 통해 이미 그려진 정적 HTML을 리렌더링(Re-rendering) 없이 사용자와 상호작용 할 수 있는 다이나믹 DOM 상태로 바꿔준다.

코드 분할

코드 분할은 말 그대로 코드 전체를 로드하지 않고 분할해서 필요에 맞게 번들로 나눠 가져오는 것을 의미한다.
필요한 번들만 가져오기 때문에 로딩 속도가 빨라지는 장점이 있다.

Nuxt 앱에서는 최초 접근할 때 서버로부터 정적 HTML을 받았지만, 다른 페이지로 이동할 때는 클라이언트 사이드에서 렌더링을 한다.
이때 렌더링용 자바스크립트 번들이 필요한데, 유니버설 모드에서는 자동 코드 분할을 통해 당장 방문할 페이지의 렌더링에 필요한 번들만 서버로부터 가져온다.

다운로드한 번들들은 캐싱이 돼서 재활용이 가능하기 때문에 서버에 재요청할 필요가 없다.

프리패칭

그렇다면 Nuxt는 렌더링을 당장 준비해야 하는 페이지를 어떻게 구분하고 있을까?
사용자가 <NuxtLink>를 클릭하면 이동할 페이지를 위한 렌더링용 자바스크립트 번들을 받아올까?
서버에서 완전히 렌더링된 HTML 파일을 불러오는 것보다 렌더링에 필요한 번들만 가져오는것이 더 빠르겠지만 그래도 네트워크 지연은 발생한다.
그래서 Nuxt는 좀 더 매끄러운 페이지 이동을 위해서 화면에 보이는 <NuxtLink>들에 한해서 해당 페이지들을 렌더링하는데 필요한 파일들을 미리 서버에 요청한다.
실제로 인터섹션 옵저버(intersection Observer)를 사용하기 때문에 뷰포트(Viewport)에 이동할 페이지 링크가 보이기만 하면 서버에 바로 번들을 요청한다.
덕분에 사용자가 링크를 인지하고 클릭하기도 전에 이미 클라이언트 사이드 렌더링을 할 준비가 되어있는 것이다.
이렇게 사용자가 패칭을 요구하기 전에 미리 패칭하기 때문에 프리패칭이라고 한다.

뷰포트에 새로운 <NuxtLink>가 등장할 때마다 서버로부터 렌더링에 필요한 자바스크립트를 받아오는 장면이다.
이 덕분에 보이는 것처럼 빠르게 페이지를 이동할 수 있다.

유니버설 배포

위에서 설명한 내용들을 요약하면 유니버설 모드는 하이드레이션, 코드 분할, 프리패칭을 통해 웹 서비스 페이지 첫 접근시 서버 사이드 렌더링을 사용하고 이후 페이지 간 이동에는 클라이언트 사이드 렌더링을 사용하는 모드라는 것이다.

추가적으로, 유니버설은 배포 대상 설정에 따라 서버 측에서 렌더링하는 방식인 서버 사이드 렌더링과 빌드 단계에서 프리렌더링(Pre-rendering)하는 방식인 정적 사이트 제네레이션(Static Site Generation)으로 한 번 더 나뉜다.
서버 사이드 렌더링은 클라이언트 측으로부터 요청이 올 때마다 렌더링에 필요한 데이터들을 서버에서 asyncDatafetch 를 통해 새롭게 가져와서 페이지를 렌더링한다.

// nuxt.config.js
export default {
  target: 'server', // 서버 사이드 렌더링
}

반면에 정적 사이트 제네레이션은 빌드 단계에서 렌더링에 필요한 데이터들을 가져와 페이지를 렌더링 해놓기 때문에 클라이언트가 요청하기 전에 이미 정적 HTML을 가지고 있다.
이후 클라이언트로부터 요청이 오면 서버에서 리렌더링 없이 빌드 단계에서 이미 만들어진 정적 HTML을 반환하므로 서버 사이드 렌더링보다 더 빠르게 반응할 수 있다.

// nuxt.config.js
export default {
  target: 'static' // 정적 사이트 제네레이션
}

이러한 성질들 때문에 데이터가 자주 변하는 웹사이트를 구축한다면 서버 사이드 렌더링이 적합하고 블로그와 같이 데이터가 자주 바뀌지 않는 경우에는 정적 사이트 제네레이션이 더 적합하다.

profile
Always happy coding 😊

0개의 댓글