Nuxt3 빌드 산출물은 Client + Server 폴더로 나뉜다
페이지 진입 시 Server side에서 Rendering을 완료하여 브라우저로 내려주고, (Pre-redering) 이후 해당 페이지를 조작할 수 있는 JS Bundle을 추가로 내려준다 (Hydration).
Hydration이 일어나기 전까지 해당 페이지는 아무 반응성을 가지지 못하는 Static한 HTML일뿐이다.
서버 시작 : 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 생성완료
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를 테스트해본 결과,
[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의 렌더링 모드인 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' 을 주어 설정하면 된다.
SSR이 SEO에 최적화 되어있다고 해서 Nuxt를 선택했고 정말 그런지 검증이 필요하여 검색 엔진을 테스트할 수 있는 무료 툴들을 몇 가지 사용해봤다.
난 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에 따라 데이터 세팅을 해주면 되지 않을까?
사실 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' }],
}
nuxt3로 마이그레이션 중인데 정말 잘 봤습니다.