Nuxt3 정리

김지우·2022년 6월 29일
8

삽질은 계속된다.

Nuxt는 어떻게 돌아가는가

Nuxt3 빌드 산출물은 Client + Server 폴더로 나뉜다
페이지 진입 시 Server side에서 Rendering을 완료하여 브라우저로 내려주고, (Pre-redering) 이후 해당 페이지를 조작할 수 있는 JS Bundle을 추가로 내려준다 (Hydration).
Hydration이 일어나기 전까지 해당 페이지는 아무 반응성을 가지지 못하는 Static한 HTML일뿐이다.

Nuxt Lifecycle

1. Server (어플리케이션의 모든 초기 request 시 실행)

서버 시작 : nuxt start
생성 프로세스 시작 : nuxt generate
Nuxt hooks
serverMiddleware
plugins(Server) : nuxt.config.js 정의된 순서대로
nuxtServerInit : Server의 첫 LifeCycle Hook. Entry Point의 Store Action을 위한 매서드. (index.js 에서만 정의가능)
middleware : 페이지 컴포넌트 렌더링 전 호출. 렌더링 조건에 활용되며, 전역, 레이아웃, 페이지 등 각각에 정의가능
validate : 페이지 컴포넌트 렌더링 전 호출. 동적 라우팅 파라미터(dynamic route parameter) 정의에 유용하다.
asyncData, fetch : 페이지 컴포넌트 렌더링 전 매번 호출. 반환값이 컴포넌트 data() 프로퍼티에 병합(merge).
beforeCreate, created : Vue LifeCycle 메서드. beforeCreate는 Vue 인스턴스 초기화 시 호출.
new fetch : Vue 인스턴스 생성 이후 Store 갱신. (Nuxt 2.12 버전에 반영)
render : routeContext. 상태 일렬화(Nuxt.js hook)
render : route. HTML 렌더링(Nuxt.js hook)
render : routeDone. HTML을 브라우저로 보냄
generate : before. HTML 파일들을 생성 (Nuxt.js hook)
generate : page. HTML 편집가능
generate : routeCreated. Route 생성
generate : done. 모든 HTML 생성완료

2. Client (Nuxt 모드에 상관없이 브라우저에서 실행되는 LifeCycle)

HTML 파일을 받음. assets 로딩(.css, .js 등)
Vue Hydration (JS 반영)
middleware : 미들웨어 또는 라우트 미들웨어.
plugins(Client) : nuxt.config.js 정의된 순서대로
asyncData : 페이지 컴포넌트 로딩 전 호출. 비동기로 호출되며, 반환값이 컴포넌트 data() 프로퍼티에 병합(merge).
beforeCreate, created : Vue LifeCycle 메서드. beforeCreate는 Vue 인스턴스 초기화 시 호출.
new fetch : Vue 인스턴스 생성 이후 Store 갱신. (Nuxt 2.12 버전에 반영)
beforeMount, mounted : Vue LifeCycle 메서드

middleware : 미들웨어 또는 라우트 미들웨어.
asyncData : 페이지 컴포넌트 로딩 전 호출. 비동기로 호출되며, 반환값이 컴포넌트 data() 프로퍼티에 병합(merge).
beforeCreate, created : Vue LifeCycle 메서드. beforeCreate는 Vue 인스턴스 초기화 시 호출.
new fetch : Vue 인스턴스 생성 이후 Store 갱신. (Nuxt 2.12 버전에 반영)
beforeMount, mounted : Vue LifeCycle 메서드

나는 Nuxt3 + Composition API 를 사용하여 작성했는데, 요 때는 위와 같은 이름이 아니라 접두어 use를 붙인 함수를 구현해야한다. (ex. useFetch, useAsyncData). Data Fetching 관련해서는 Nuxt3 공식 홈페이지에 example이 많이 있다.

아무튼 직접 fetch와 asyncData를 테스트해본 결과,

  • fetch의 경우 Server side와 Client side 각각에서 호출이 되며,
  • asyncData의 경우 Server side에서 한번 호출된다
[PAGE1][ASYNCDATA] This is from Server
[PAGE1][FETCH] This is from Server
[PAGE1][FETCH] This is from Client // Client
[PAGE2][ASYNCDATA] This is from Server
[PAGE2][FETCH] This is from Server 
[PAGE2][FETCH] This is from Client // Client

결과적으로 위 Lifecycle 도식과 일치하게 동작한다는 뜻이다.

여기에, NuxtLink를 사용하면 Nuxt의 Universal mode (SSR + CSN) 동작 상 PreFetch 되어 페이지 이동이 CSR로 일어난다.
즉, Page1 -> Page3 의 이동이 NuxtLink 로 구현되어있다면,
1. Page1 이 렌더링 될 때 Page3 PreFetch 됨
2. Page3 진입 시 해당 페이지에 대한 Client Rendering 동작이 일어남

Nuxt 렌더링 모드/배포 옵션

Nuxt의 렌더링 모드인 Universal mode 고전적인 SSR의 단점(페이지 이동 시 블링킹)을 극복하기 위해 첫 화면을 SSR로, 페이지 Navigate는 CSR로 하여금 SSR+CSN(Client Side Navigation)로 구동하는 방법이다. 이 모드는 또 두 가지 옵션으로 나뉘는데, Dynamic SSR 과 Static 이 있다. (모드에 따라서 Run하는 script도 다르다.)

  • Dynamic SSR : 기본 설정 값. 브라우저 요청이 들어올 때마다 server side rendering을 함(fetch, asyncData 등). 요청 시마다 데이터가 갱신된다고 생각하면 된다. (>>nuxt start)

  • Static: 웹 사이트 빌드 시에 Pre-rendering을 한다. 그 때 server side lifecycle이 돌게되고, 브라우저 요청이 들어오면 이미 렌더링 된 것을 뿌려주니 Dynamic SSR 보다는 빠르다. 말 그대로 정적인 사이트 구축에 적합하다. (>>nuxt generate)

    이 배포 옵션은 nuxt.config.js 에서 각각 target:'server', target:'static' 을 주어 설정하면 된다.

SEO

SEO 테스트 Tool

SSR이 SEO에 최적화 되어있다고 해서 Nuxt를 선택했고 정말 그런지 검증이 필요하여 검색 엔진을 테스트할 수 있는 무료 툴들을 몇 가지 사용해봤다.

  • SEO Site Checkup : SEO를 위한 항목별 체킹을 해준다. 무료긴 한데 자꾸 돈내놓으라고 함.
  • SEOquake : 최종 선택안. SEO Site Checkup만큼 디테일한 테스트 항목을 제공하진 않지만 무료로 제공되는 기능 안에선 충분히 원하는 정보를 얻을 수 있었음. 크롬 확장프로그램으로 제공된다.
  • Lighthouse : 크롬 개발자 도구에서 볼 수 있음. SEO를 비롯해서 전반적인 웹 사이트의 퍼포먼스 (속도, 접근성 등)를 측정해줌.

난 SEO 측면의 데이터만 필요했기에 SEOquake를 사용했다. Server side Lifecycle hook 인 asyncData에다 store에 데이터를 때려박는 코드를 넣고 SEOQuake를 돌려보니, 프로젝트를 CSR로 설정하고 돌렸을 때는 잡히지 않던 키워드들이 더 잡혔다. (프로젝트를 CSR로 설정하려면 nuxt.config.js에 SSR : false 값을 주면 된다.) 물론 요즘 검색 엔진은 좋아져서 구글의 경우 크롤링 시 HTML 뿐만아니라 js까지 실행해준다고는 하던데 잘 되는지는 모르겠다. 확실하지 않으니 동적 데이터의 경우 server side hook에서 처리하여 집어넣어주는 게 나을 듯 싶다.

그런데 의문점이 하나 있다면, 앞서 말한 의 경우 검색 엔진이 진입했을 때는 SSR이 되지만 이전 페이지에서 navigate 된 경우 데이터를 넣기 위한 server side hook인 asyncData는 호출되지 않는다. 그 경우 어느 지점에서 데이터를 깔아줘야 할까? 만약, client side에서 넣어준다고 하면 검색 엔진에게 detect 되지 않으니 SEO 측면에서 옳은 방향이 아닌 듯 하고.. server side에서 넣어주자니 navigate 시 호출되지 않으니 hook을 타지 않으므로 아예 데이터 안들어갈 것이다. 이걸 우짜지? 그렇다고 첫 페이지 server side hook에서 navigate 페이지에서 사용할 데이터까지 전부 넣자니 HTTP 응답이 늦어지면 TTV에 영향을 줄 것이다. 이 점은 고민이 필요한 것 같다.

정답은 아니겠지만, if(process.server) 를 이용하면 server에서 실행되는지 client에서 실행되는지 주체를 알 수 있다. 그러니까 fetch의 경우 양 측에서 한 번씩 호출이 되므로 렌더링 여부(데이터 initialize 여부)를 어딘가에(아마 store) 전역으로 저장해놓으면, SSR을 통해 process.server == true인 상태로 렌더링이 될 때 데이터 init 후 flag 값을 변경하고, 후에 CSR fetch 때 (process.server == false) flag에 따라 데이터 세팅을 해주면 되지 않을까?

meta 설정

사실 CSR이건 SSR이건 meta 설정이 가장 핵심인 것 같다. Nuxt에선 개별 페이지 별로 meta를 설정할 수 있고, global 하게 nuxt.config.js에서 meta를 줄 수도 있다. 아래와 같이 head 속성에서 설정하면 된다.

head: {
       title: 'nuxt template',
       meta: [
           { 
               charset: 'utf-8' 
           }, 
           { 
               name: 'viewport', 
               content: 'width=device-width, initial-scale=1' 
           },
           {
               hid: 'description',
               name: 'description',
               content: '소개 문구',
           },
           {
               name: 'title',
               content: '타이틀',
           },
           {
               name: 'keywords',
               content: '키워드',
           },
           {
               name : 'author',
               content: 'jiwoo.kim',
           },
       ],
       link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
   }

출처

profile
천방지축 어리둥절 빙글빙글

1개의 댓글

comment-user-thumbnail
2023년 12월 20일

nuxt3로 마이그레이션 중인데 정말 잘 봤습니다.

답글 달기