본 포스팅은 NomadCoders(노마드 코더)의 NextJS 강의를 기반으로 작성되었습니다.
[#1 - Defining Routes]
기존에 React를 사용하여 Page Routing 구조를 설계하고 구현하는데에 꽤나 많은 노력을 들였던 것으로 기억한다. React 에서는 React Router 패키지를 설치한 후 app.jsx 파일에 import 해주며 Route와 Path,Element 속성을 통해서 구현했었다. 하지만 NextJS에서는 무척 간단하고 편리하게 Routing을 제공한다. 폴더 구조를 통한 Routing을 제공하는데, 단순히 app 폴더 내부에 원하는 경로 이름으로 된 폴더를 생성해주고 내부에 UI를 그려줄 page 파일만 생성하면 Routing 구현이 끝난다. NextJS는 React Framework이기 때문에 프레임워크가 개발자의 코드를 호출하여 지정된 구조 및 틀에 코드를 끼워맞추어 구동한다. 따라서 정해진 틀에 맞게 작명을 하고 폴더 구조를 맞춰서 설계해주면 어려운 기능들을 쉽게 구현할 수 있다.
[#2 - Not Found Routes]
NextJS는 폴더 구조를 따라 페이지 라우팅을 구동하는 프레임워크이다. 따라서 최상위 폴더명을 반드시 app으로 설정하고 새로운 경로를 생성하며 라우팅을 구현하고 싶다면 원하는 경로명으로 폴더를 생성하고 해당 폴더 하위에 반드시 UI를 그려주는 파일인 page.tsx(.jsx) 파일을 생성해주어야 한다. 기본적인 라우팅 흐름에 대해서는 이해가 되었지만 한 번 더 짚고 넘어가야할 부분이 있다.
라우팅 구조를 생성하면서 최상위 폴더인 app 폴더 하위에는 반드시 있어야하는 파일 3가지가 존재한다. page, layout, not-found 파일이다.
- page 파일은 앞서 말했듯이 해당 폴더 경로로 이동하였을 때 그려지는 UI를 담당하는 파일이다.
- layout 파일은 NextJS 라우터가 찾는 특수 파일로 모든 페이지에 공통적으로 적용되는 UI를 정의하는 파일이다. html과 body 요소를 반환하고 body 내에서 children을 동적으로 렌더링한다.
- not-found 파일은 React 프로젝트를 할 때 생성하는 오류 페이지와 같은 기능을 한다. 올바르지 않은 url 주소를 입력하거나 존재하지 않는 페이지를 렌더링할 때 보여지는 페이지이다.
라우팅과 관련해서 유용하게 사용되는 hook이 있는데 바로 usePathname이다. 해당 hook은 현재 사용자가 위치해있는 페이지의 라우팅 주소를 반환해준다. 주의해야할 점은 해당 hook은 client component에서만 작동하기 때문에 사용할 파일의 최상단에 "use client"를 입력해주어 client component로 사용한다는 것을 명시해주어야 한다.
[#3 - SSR vs CSR]
Rendering(렌더링)
- Rendering이란 React Code를 브라우저가 이해할 수 있는 html로 변환해주는 것을 말한다.
CSR(Client Side Rendering)
- 모든 렌더링, UI 구축 작업이 Client 측에서 일어나는 방식이다. 페이지의 소스 코드를 보면 실제로는 비어있는 HTML 상태이고 Client가 Javascript를 로드하고 그 후에 Javascript 가 UI를 브라우저에 빌드한다. 해당 방법은 여러가지 단점이 존재한다.
1. 렌더링 해야할 파일의 용량이 매우 크거나 인터넷 연결 환경이 좋지 못하다면 Javascript 파일을 다운 받는데에 오랜 시간이 걸리기에 사용자가 빈 화면을 보는 시간이 길어질 수 있기에 UX 측면에서 좋지 못한 영향을 받을 수 있다. 또한 가능성이 매우 적기는 하지만 사용자가 웹 브라우저의 Javascript를 비활성화 시켜놓았다면 웹 페이지가 제대로 렌더링 되지 못하는 경우가 발생할 수도 있다.
2. SEO(Search Engine Optimization) 측면에서 좋지 못한 경향이 있다. SEO란 검색 엔진 최적화를 뜻하는 말로 검색을 하였을 때 해당 페이지가 검색 결과에 노출이 얼마나 잘되는지에 영향을 준다. CSR 방식으로 렌더링이 되는 웹 페이지는 렌더링이 된 직후에는 Javascript 파일이 다운로드 되어있지 않기 때문에 빈 파일 상태이므로 검색 엔진이 찾아낼 데이터가 없는 것이다. 따라서 SEO 측면에서 좋지 못한 면이 있다.
SSR(Server Side Rendering)
- CSR과 반대로 모든 작업이 Server 측에서 일어나는 방식이며 NextJS는 자동적으로 SSR 방식으로 실행이 가능하다. 웹 페이지가 렌더링이 되었을 때 소스 코드를 보면 실제 파일의 모든 데이터들이 들어있는 것을 확인할 수 있다. CSR로 동작하는 React는 html 파일 내부에 하나의 태그만 존재했지만 NextJS로 생성된 파일은 데이터들이 모두 들어있다. 앞서 말했던 Javascript 비활성화 되어있는 웹 브라우저에서도 Javascript를 브라우저 측에서 다운로드 받을 필요가 없기 때문에 문제없이 동작한다. 조금 상세하게 알아본다면 page 파일에 있는 모든 컴포넌트들을 Server 측에서 html로 생성하여 주기 때문에 UI를 빌드하는 과정에 Javascript가 필요가 없는 것이다.
위의 두 개념에 대해서 알아보았지만 헷갈려하지 말아야 할 부분이 있는데, Compnent들을 생성하다보면 Client 측 Component가 있을것이고 Server 측 Component가 있을 것인데 그 이전에 Rendering 자체는 모두 Server 측에서 한다는 부분이다.
[#4 - Hydration]
Hydration
- 정적 HTML을 React Application으로 초기화하는 작업을 말한다. Hydration이라는 단어는 '수분 공급'이라는 뜻을 가진 단어로 건조한 HTML 파일에 JS 코드를 물과 같이 공급하여 생기있게 만들어주는 것처럼 이해하면 쉽다. 좀 더 구체적으로 말하자면 SSR에 의해 Server 측에서 렌더링된 정적 페이지 HTML과 번들링 된 JS 파일을 Client 측으로 보내면 Client 측에서 HTML 코드와 React(JS)파일을 매칭시켜서 페이지를 상호 작용이 가능한 동적인 페이지로 만드는 과정을 말한다.
쉬운 예시를 하나 보면 기존에 페이지 이동을 위해서는 HTML의 anchor 태그를 통해 특정 페이지를 호출 및 렌더링하며 이동하였다. 하지만 Hydration 과정을 거치면서 NextJS 프레임워크나 React 등의 JS로 Navigate 기능을 사용한다면 페이지 이동 간에 재렌더링이 필요없이 Client 측에서 더 빠르고 불필요한 페이지 재렌더링을 하지 않고 페이지 이동이 가능한 것이다.
웹 페이지 로드에 대해서 단계적으로 파악을 해보면 다음과 같다.
1. 웹 페이지 접속 -> UI를 갖고 있는 정적 HTML 파일 렌더링(React Components를 정적인 HTML로 표시)
2. 프레임워크(NextJS)의 페이지 Loading 시작
3. React, Components등 데이터 로드 및 초기화
4. 정적 HTML의 React화 -> Interactive한 페이지로 변화
SSR로 인한 Server 측에서 모든 page와 component를 초기 상태에서 rendering -> 정적 HTML로 Client에 전달 -> 뒷단에서 프레임워크 초기화 -> 정적 HTMl 위에 react application 입히기 -> 기능이 활성화되고 상호 작용이 가능한 페이지로 변화
앞서 말한 설명들이 두서 없이 작성되고 너무 어려운 내용 같아 보여서 혼선을 줄 것 같아 쉽게 정리를 하고 마무리해보자. 정리하자면 정적 HTML 파일이 렌더링 된 후에 마른 식물에 물을 주듯이 JS를 통한 기능을 부여하여 페이지를 초기화한다. 이후 상호 작용이 가능한 동적인 React화 된 웹 페이지로 전환하는 과정을 Hydration이라고 한다. 정적인 HTML 파일 위에서 React를 실행한다고 보면 되겠다.(정적이고 단순한 페이지 -> 동적이고 활발한(?) 페이지)
[#5 - "use client"]
Components에는 Client 측에서 렌더링되는 Client Components와 Server 측에서 렌더링되는 Server Components가 있다. NextJS는 Server Side Rendering이 가능한 React Framwork이기 때문에 프로젝트 내부의 파일들을 모두 Server 측에서 렌더링하여 Hydration 과정을 통해 사용자가 웹 페이지를 사용할 수 있게 해준다.
하지만 일반적인 경우를 생각해보았을 때 웹 페이지를 사용하는 사용자 입장에서 렌더링 된 페이지 내부의 모든 Components를 사용하지는 않는다. 또한 버튼, 입력창, 링크 등과 같이 사용자의 동작에 맞춰 상호 작용하는 요소나 Components와 달리 사용자와 상호 작용 자체가 필요없는 일반 정보성 텍스트 파일 같은 경우 Server 측에서 매번 재렌더링해서 Client 측으로 보내줄 필요가 없고 효율성이 떨어지며 성능과 속도 또한 저하가 된다.
이러한 경우 사용하는 키워드가 "use client"이다. 파일의 최상단에 "use client"라는 문구가 적혀있다면 해당 파일 및 Components는 Server측이 아닌 Client 측에서 렌더링된다. 이 말은 backend에서 render되고 frontend에서 hydrate가 된다는 뜻이다. 즉 사용자와 상호 작용이 빈번하게 일어나고 사용자가 자주 사용하는 요소에 대해서는 효율성과 최적화를 생각하여 Server 측에서 Components들을 계속 보내주는 것이 아니라 Client 측에서 렌더링하여 처리하겠다는 뜻이다. 그렇게 되면 렌더링 과정에서 굳이 변화하지 않는 요소나 Components들의 JS 파일을 불필요하게 다운로드 할 필요가 없어서 렌더링 시 다운 받을 JS 파일의 크기가 줄어들며 웹 페이지가 최적화 되고 렌더링 속도에도 좋은 영향을 주어 성능적으로 더 뛰어나고 효율적인 프로그래밍이 가능해진다.
쉽게 정리하자면 이벤트가 자주 발생하는 요소는 빈번하게 렌더링이 된다. 따라서 사용자가 사용하는 요소가 많은 파일 및 Components 파일의 코드 최상단에는 "use client"를 입력하여 Client 측에서 렌더링이 되도록 Client Components화 해주는 것이 성능적인 측면에서 좋다.
("use client" 명령어를 사용하면서 헷갈리지 말아야할 부분이 있다. 본 명령어를 사용한 파일은 Client 측에서만 Rendering된다는 의미가 아니라 Client 측에서"도" Rendering 된다는 점을 헷갈려서는 안된다. NextJS에서는 모든 Components들은 기본적으로 Server 측에서 PreRendering 되어 Client측으로 전달된다.)
[#6 - Recap]
1. NextJS는 사용자가 페이지에 진입하고 사용자에게 페이지에 대한 응답이 주어지기 전에 backend에서 페이지에 대한 파일을 Pre-Rendering한다.
2. 모든 Components를 가져가서 정적인 HTML로 변환하여 사용자에게 준다.
3. 이후 Framework와 React를 초기화한다.
4. "use client" 명령어를 가진 Components가 Hydrate된다.
Server Components 안에 Client Components를 갖는 것은 가능하지만 그 반대는 '아직은' 불가능하다. 가능한 이유는 "use client" 명령어가 입력된 파일은 하위 구성요소를 포함하여 해당 파일로 가져온 다른 모든 모듈이 클라이언트 번들의 일부로 간주되기 때문에 하위 요소는 모두 Client Components 취급을 받기 때문이다.
[#7 - Layouts]
NextJS는 필수적으로 Layout 파일을 갖는다. 기본적으로 웹 페이지가 렌더링 될 때 NextJS는 가장 먼저 layout.tsx(.jsx) 파일을 찾은 후 Layout Components를 렌더링한 후 url을 보고 필요한 폴더의 page 파일을 찾아 렌더링할 components를 발견한 후 html, body 태그 내부의 {children} 요소에 넣어주면서 해당 페이지를 렌더링 해준다.
Layout 파일들은 서로 상쇄시키는 것이 아니라 중첩된다.
파일명이 서로 다른 폴더 하위에 있어도 layout으로 동일하기 때문에 헷갈리다는 단점도 존재한다.
프레임워크는 이동하려는 page에 가장 가까운 layout을 찾고 상위 경로를 확인한 후 중첩할 layout 파일을 확인하며 최상위 경로에 다다르면 중첩된 layout 파일을 렌더링하면서 보여줄 page 파일의 Components를 렌더링해준다.
[#8 - Metadata]
Route Groups
우리는 NextJS는 기본적으로 폴더 구조를 통한 Routing이 이루어진다는 것에 대해서 알고 있다. 따라서 프로젝트의 최상위 폴더는 app 폴더이며 폴더를 생성하면 폴더명에 따라 URL이 생성되고 Routing 구조가 생성된다. 또한 폴더 내에 page 파일은 해당 경로로 이동하였을 때 UI를 그려내주는 파일이다. 특정 경로를 생성하기 위해 폴더를 생성하였다면 그 폴더 내부에는 반드시 page 파일이 있어야 렌더링이 가능해진다. 여기까지는 기본적인 NextJS에 대한 부분이다.
app 폴더에는 반드시 page, layout, not-found 파일이 존재해야한다. 이 때 우리는 Route Group을 통해서 주소 경로를 생성하지 않고 폴더를 생성하여 UI를 그려주는 page 파일과 별도의 Components 파일을 관리할 수 있다. 방법은 폴더를 생성할 때 폴더명을 소괄호 내부에 적어서 생성하는 것이다. 그렇게 생성하면 NextJS는 해당 파일을 경로로 인식하지 않고 단순 폴더로 인식하여 오류가 발생하지 않는다. 따라서 app 폴더 내에 (home) 폴더를 생성하여 page 파일과 별도 Components가 담긴 파일들을 넣어두면 주소에 따른 파일 관리가 쉬워지게 된다.
Metadata
Metadata는 웹 페이지의 Title과 해당 페이지에 대한 설명 정보가 담긴 일종의 객체를 의미한다. 일반적인 Components에서는 metadata 객체를 내보낼 수 없고 page나 layout 파일에서만 내보낼 수 있으며 Server Components에서만 설정이 가능하다. 우리가 HTML을 공부할 때 meta 태그에 대해서 본 적이 있을 것이다. meta 태그의 역할은 브라우저와 검색 엔진을 사용할 수 있도록 웹 페이지의 정보를 담고 있으며 쉽게 말해 웹 페이지의 요약 정보라고 보면 된다. NextJS에서도 마찬가지로 프레임워크로서 향상된 SEO를 위해 웹 페이지에 대한 정보를 객체 형태 + metadata라는 이름으로 저장하여 사용한다. metadata 객체의 속성으로 title과 description을 일반적으로 사용하는데 title은 HTML의 head/title 태그와 같은 역할로 페이지의 이름을 설정하는 속성이고 description은 페이지 자체에는 표시되지 않지만 SEO의 향상을 위한 페이지 정보를 담고 있는 속성이다. 추가적인 정보로 NextJS에는 Metadata API가 존재한다. 따라서 TS로 NextJS를 사용하는 경우에 metadata의 Type을 별도의 정의 없이 Metadata로 설정하여 사용하는 것이 가능하다.
metadata는 병합되므로 서로 다른 파일에서 하나의 페이지에 대한 metadata를 각각 설정해주어도 하나로 병합되어 페이지에 적용이 된다는 특징도 있다.
[#9 - Dynamic Routes]
앞서 우리가 NextJS가 기본적으로 제공하는 폴더 구조에 따른 Routing을 보았을 때, 우리가 설정한 폴더명에 따라서 자동적으로 url을 NextJS가 인식하여 Routing 해주는 것을 볼 수 있었고 그렇게 생겨나는 url을 Static Routing(정적 라우팅)이라고 표현한다. 쉽게 말해 우리가 지정해둔 이름으로 웹 페이지 주소가 생성되고 절대적인 값으로 사용이 되기 때문에 Static(정적)이라고 표현한다. 하지만 우리는 React에서도 다루어 보았듯이 Dynamic Routing(동적 라우팅)을 구현해보고 싶고 또 해야만 일반적인 서비스를 구현할 수 있다. Dynamic Routing은 특정 페이지의 id 값이나 페이지만의 특정 값을 통해서 주소가 생성되는 것을 의미한다. Static Routing은 "localhost:8000/home", "localhost:8000/user"과 같은 형태였다면 Dynamic Routing은 "localhost:8000/user/123?lan=kr&page=2"과 같이 주소가 동적인 상태를 띄는 형태이다. NextJS에서는 정말 간단하게 폴더 구조를 통해서 Dynamic Routing을 구현할 수 있다.
일단 구현하고자 하는 경로의 최상단을 소괄호를 이용해 생성하고 내부에 하위 경로로 설정할 이름의 폴더를 생성한다.
다음으로 그 내부에 대괄호를 이용한 속성값이 들어간 폴더명으로 폴더를 생성해준다. 속성값 폴더 내부에 page 파일을 생성하면 우리가 원하는 동적인 라우팅을 구현할 수 있다. 아래와 같은 형태로 사용이 가능하게 된다.
이 때 우리가 page 파일에서 props를 받아 console 창에 출력을 해보면 params, searchParams 두 가지 속성이 들어있는 객체를 볼 수 있다. params 속성은 우리가 url 주소에 입력한 동적인 속성값을 의미하고 searchParams는 url 주소 최후방에 상세 검색 정보에 대한 데이터들이 담겨 있는 속성이다. 아래와 같이 출력이 된다.
여기서 잊지 말아야 할 부분은 해당 page 파일은 앞서 다루었던 "use client" 명령어가 붙지 않은 Server Components이기 때문에 console.log를 통해 출력을 하더라도 브라우저 내에서는 출력이 되지 않고 현재 우리가 작업중인 IDE에서 확인이 가능하다.
'NextJS' 카테고리의 다른 글
[NextJS] - (2) Page Router (2) | 2024.09.10 |
---|---|
[NextJS] - (1) NextJS (1) | 2024.09.10 |
[NextJS(v.14)] - (3) Data Fetching (0) | 2024.08.20 |
NextJS(v.14) - Routing(1) (4) | 2024.07.06 |
NextJS(v.14) - Project Setup (1) | 2024.07.05 |