
현재 많은 기업에서 사용되고 있는 안정적인 라우터
리액트 라우터처럼 특정 조건을 기준으로 웹 서비스 내 페이지를 분할하고,
또 그렇게 분할된 페이지 간의 이동을 처리하는 등의 페이지 라우팅 처리
그 이름에 걸맞게 Pages라는 폴더의 구조를 기반으로 페이지를 라우팅
이게 무슨 말이냐면,
pages 폴더 아래에 위와 같은 자바스크립트 파일이 있으면,
자동으로 이 파일들의 경로와 이름에 따라서 페이지 라우팅이 제공
예를 들면,
브라우저에서 / 경로로 접속을 요청하면,
➡️ index.js 파일에 있는 컴포넌트가 페이지로써 화면에 렌더링되고
브라우저에서 ~/about 경로로 접속을 요청하면,
➡️ about.js 파일에 있는 컴포넌트가 페이지로써 화면에 렌더링됨
이렇듯 페이지 라우터는 pages라는 폴더 아래에 들어있는 파일명 기반으로 페이지 라우팅을 자동으로 제공
폴더의 이름으로도 페이지 라우팅을 설정할 수 있음
~/about이라는 경로로 요청이 오면,
pages 폴더 아래에 about.js라는 파일이 없으니까
about 폴더 아래 index.js 파일에 있는 컴포넌트가 페이지로 렌더링됨
마찬가지로,
~/item이라는 경로로 요청이 오면
pages 폴더 아래에 item.js라는 파일이 없으니까
item 폴더 아래 index.js 파일에 있는 컴포넌트가 페이지로 렌더링됨
또한, 동적 경로를 갖는 페이지의 라우팅도 간단하게 설정 가능
경로상에, 변할 수 있는 가변적인 값을 포함하고 있는 경로
➡️ 블로그의 게시글 페이지나, 쇼핑몰의 상품별 상세 페이지 같은 곳에 자주 활용됨
Next.js의 페이지 라우터에서 이렇게 동적 경로에 대응하는 페이지를 만들고 싶다면
[id].js라는 파일을 만들면,
이 파일은 동적 경로에 대응하는 페이지의 역할을 하게 됨
따라서 동적 경로에 있는 가변적인 값이
대괄호 속에 있는 id에 하나씩 맵핑되는 식으로 페이지 라우팅이 설정됨
npx create-next-app@14 section02npx: Node Package Executor
npmjs.com(라이브러리들이 실제로 올라가 있는 서버)에 등록된 최신 버전의 node package를 다운로드 없이 바로 실행시키는 명령어create next app: next.js 공식 문서에서 안내하고 있는 새로운 넥스트 앱을 생성하는 node.js 패키지
그러니까 보일러 플레이트 같은 것➡️ 즉, npx라는 도구를 사용해서 create next app이라는 새로운 next.js 앱을 생성할 것
(15가 나왔지만, 이 당시 더 안정적인 14버전을 사용)section02는 패키지 이름
설치할 라이브러리는 위와 같이...
거의 기본이었던 것 같음
import alias의 경우,
절대 경로로 모듈을 import할 수 있도록 도와주는 기능
예를 들어
import A from "@/components/~"
라고 사용하면,
@는 /src 경로를 의미하기 때문에
더 간결하게 사용이 가능
이렇게 프로젝트를 만들면
next.js도 node.js의 패키지이기 때문에
의존성을 보관하는 node_modules 폴더나,
패키지 정보를 보관하는 package.json이란 파일이 존재
스크립트도 설정하고,
설치할 패키지도 명시
이 프로젝트를 개발 모드로 실행할 때는
npm run dev
로 실행함
next.js는 어쨌든 React를 기반으로 만들어진 확장판 개념의 프레임워크이기 때문에
next.js도 React와 흡사한 폴더 구조를 가지고 있음
public 폴더
: 파비콘, 로고, 페이지 내부에서 사용할 이미지 파일과 같은 정적인 파일 보관src 폴더
-> pages 폴더: 페이지 역할을 할 컴포넌트를 보관하는 파일을 경로에 맞게 보관
-> styles 폴더: 컴포넌트나 앱의 전체적인 스타일을 담당하는 css 파일 보관
인덱스 경로의 페이지 역할을 하는 index.tsx 파일에 들어가,
Home이라는, default로 내보내진 페이지 역할을 하는 컴포넌트가 작성되어 있음
이 Home 컴포넌트가 리턴하고 있는 HTML 요소들이 우리가 next.js 앱에서, 인덱스 페이지에 접속했을 때 나타나는 요소들
(왜인지 모르겠지만 이미지가 전부 깨지고 있다...)
그래서 일단 이미지를 다 지워줌
만약 <main> 태그 안 쪽에 <h1> 태그를 추가해보면,
잘 렌더링 됨
페이지 라우팅 실습을 해보면,
pages 폴더 아래에 test.tsx라는 파일을 생성하고,
해당 페이지에 컴포넌트 작성
파일명인 /test 경로로 접속하니,
위에 작성된 페이지 컴포넌트가 화면에 렌더링됨
_app.tsx와 _documnet.tsx가 뭘까?위 두 파일은, 페이지의 역할을 하는 페이지는 아니고,
넥스트 앱의 모든 페이지에 공통적으로 적용될 로직이나,
또는 공통적으로 적용될 레이아웃 또는 데이터를 다루기 위해 필요한 파일
리액트의
app.tsx와 동일한 역할
즉, 동일하게 루트 컴포넌트 역할을 함
props를 살펴 보면,
Component: 현재 페이지 역할을 할 컴포넌트
pageProps: Component에 전달될 페이지의 props를 모두 객체로 보관
따라서, return문을 보면
페이지 역할을 할 컴포넌트를 렌더링하면서,
props는 전달받은 pageProps 그대로 구조분해 할당으로 전달
결론적으로 next.js에서 어떤 페이지를 렌더링하던 간에
_app.tsx 컴포넌트 밑에 페이지 역할을 하는 컴포넌트가 렌더링되는 이 구조로 렌더링됨
이것을 브라우저로 살펴보면,
App 컴포넌트 아래에 Page 컴포넌트가 있는 걸 알 수 있음
렌더링되어야 할 페이지 컴포넌트의 page 라는 함수를 잘 전달 받고 있고
pageProps는 아무것도 없는 것까지 확인
그렇기 때문에
이 next.js 앱에서 모든 페이지에 공통적으로 타나타야 되는,
예를 들면 Header 같은 공통 요소가 필요하다면,
이 App 컴포넌트의 return 문에 추가
그러면 이 프로젝트의 어떤 페이지를 가도 해당 header가 나타남
/test 경로에도,
/ 경로에도 나타나는 걸 볼 수 있음
(만약 /에서 안 나오면, 기존 퍼블리싱 때문 + 모바일 사이즈로 볼 때 문제가 있을 수도)
(따라서 기존 요소를 다 지우거나, 하면 나옴)
따라서 이 App 컴포넌트는, 모든 페이지 컴포넌트들의 부모 역할을 하는 next.js 앱의 루트 컴포넌트
➡️ 전체 페이지에 공통으로 포함되는 (헤더 컴포넌트 같은) 컴포넌트 또는 레이아웃을 렌더링 한다거나,
비즈니스 로직을 작성할 수 있는 공간
모든 페이지에 공통적으로 적용이 되어야 하는 next.js 앱의 HTML 코드를 설정하는 컴포넌트
기존의 리액트 앱의 index.html과 비슷한 역할
위처럼 랭귀지 속성을 바꾼다든지,
혹은 모든 페이지에 다 적용이 되어야 하는 메타 태그를 설정한다거나,
폰트를 불러온다거나,
char-set을 설정한다거나,
구글 애널리틱스 같은 서드파티 스크립트를 넣는다거나, 등등의
페이지 전체에 다 적용되는 html 태그를 관리하기 위해 사용
next 앱의 설정 관리
이것은 실습을 위해 지금은 false로 끔
왜 꺼야 하냐?
➡️ 이 모드가 켜져 있으면
리액트 앱의 존재하는 잠재적인 문제를 검사하기 위해서
개발모드로 실행했을 때 컴포넌트를 두 번 실행
따라서, 콘솔을 찍는다든지
디버깅하기가 불편하기 때문에 false
앞으로는 하나의 프로젝트를 만들어보며 실습할 것!
간단한 도서 조회 및 리뷰 서비스를 만들어보자!
section02 폴더에서,
npm run dev로 서버 가동
만약 페이지에 오류가 발생하면
index.tsx 파일의 import문까지 모두 제거
그러면 말끔한 대문(?)을 마주할 수 있음
서치 페이지는 브라우저 상에서
/search 라는 경로로 접속할 수 있어야 하므로
프로젝트에 pages 폴더 아래에 새 파일로 search.tsx 파일 새로 생성
그리고 해당 파일에 컴포넌트를 만들면,
해당 경로로 요청 시 컴포넌트 렌더링
폴더로 분리하고 싶으면
해당 경로를 이름으로 한 폴더 아래 index.tsx를 만들어주면,
pages 아래에 search.tsx 파일이 없어서
search 폴더 아래에 있는 index.tsx 파일의 컴포넌트를 페이지로 렌더링
해당 경로로 요청 시 컴포넌트 렌더링되는 것 확인
이 search 페이지 밑에 더 다양한 경로를 중첩으로 만들고 싶다면,
중첩 라우팅 가능
pages/search 폴더 아래에 해당 경로로 사용할 이름으로 파일 생성
그 파일에 페이지 역할을 할 컴포넌트 생성
그러면 /search/setting 경로로 요청 시 작성한 컴포넌트가 렌더링됨
물론 폴더로 분리하는 것도 가능
아래처럼
이런 식의 URL을 본 적이 있을 것
쿼리 스트링은,
경로에서 물음표(?)와 함께 표현되며,
주로 검색 기능을 제공하는 웹 사이트에서 사용자의 검색어나 필터링 조건을 전달할 때 사용됨
페이지 경로 자체에는 영향을 주지 않고,
Next.js 프로젝트에서도 해당 페이지가 쿼리 스트링을 쓰는 페이지라고 해서
폴더 구조를 따로 변경해야 하거나, 하진 않음
대신 전달된 쿼리 스트링의 값을,
이 서치 페이지 컴포넌트에서 직접 꺼내서 사용하려면,
그때는 파일의 최상단에 useRoute라는 훅을 Next Router라는 패키지로부터 불러와야 함
import를 하려면 두 가지 패키지가 뜨는데,
이중 next/router에서 불러와야 함
(next/navigation은 App Router에서 쓸 것)
useRoute: 이름 그대로 router라는 객체를 컴포넌트 내부에서 사용할 수 있도록 반환하는 함수
➡️ router 변수 안에, router라는 객체가 저장
이 객체에는 우리가 필요한 대부분의 라우팅과 관련된 정보가 다 저장되어 있음
이 router 객체를 출력해보면,
back이나 push 처럼 뒤로가기나 다른 경로로 이동하는 메서드도 있고
객체 형태로 전달한 쿼리 스트링 값도 query 안에 잘 들어가 있음
Next.js 앱이 우리가 전달한 쿼리 스트링을 읽는 과정에, 컴포넌트를 한 번 더 렌더링시키기 때문
그래서 첫 번째로 출력된 ㅌ메시지에는,
아직 쿼리 스트링을 읽어오지 못해
쿼리에 빈 객체가 출력됨
하지만 두 번째로 출력된 메시지에는,
우리가 전달한 쿼리 스트링을 잘 읽어오고 있는 것도 확인 가능
아무튼, 이렇게 쿼리 스트링이 router라는 객체 안에 담겨있으니
코드에서 해당 스트링 값을 꺼내올 수 있음
쿼리 스트링이 화면까지 잘 렌더링되고 있음
이 쿼리스트링은 페이지 경로에는 영향을 주지 않기 때문에
디렉토리에 별도 설정이 없지만,
컴포넌트 내부에서 쿼리스트링의 값을 읽어오기 위해
useRouter라는 훅을 불러와서 라우터의 쿼리라는 프로퍼티를 통해 불러올 수 있음
Book 페이지는 localhost:3000/book/100과 같이 가변적인 값을 포함하는 동적 경로 사용
이렇게 가변적인 값을 URL 파라미터라고 함
이렇게 URL 파라미터를 사용하는 동적 경로를 갖는 페이지를 생성하려면,
위와 같이 대괄호 안에 id값이 있는 파일명 형태로 파일 생성
파일 안에 이렇게 페이지 컴포넌트를 만들어줌
위처럼 설정하면,
Next.js는 대괄호가 포함된 파일명을 보고,
이 파일은 URL 파라미터를 갖는 동적 경로에 대응하는 파일이라고 인식해서
URL 파라미터의 값이 뭐가 되든, 이 파일에 작성된 컴포넌트를 페이지로써 화면에 렌더링 시키도록 설정
1이 아니라 다른 값이어도 같은 페이지가 렌더링됨
그리고 이렇게 전달된 URL 파라미터의 값을 페이지 컴포넌트에서 꺼내서 사용하려면,
마찬가지로 useRoute 훅을 이용
이 훅의 반환값인 router 객체를 살펴보면,
query라는 프로퍼티에, 전달한 URL 파라미터가 id라는 이름으로 저장됨
앞서 살펴본 쿼리 스트링과 똑같은 방식으로 라우터 객체에 저장됨
이렇게 저장되는 이유는
대응할 파일의 이름을 [id].tsx로 저장했기 때문에 id라는 이름으로 매핑
이 id값만 따로 쓰고 싶은 경우
useRouter 훅의 반환값인 router 객체에서 파라미터 값만 가져오면 됨
가져온 id값으로
렌더링해온다든지...
localhost:3000/book/225/225/255 경로로 접속하면?
현재는 404 Not Found가 발행될 것
왜냐하면,
지금 프로젝트에서 [id].tsx 파일은 /book 경로 뒤에
딱 하나의 id값만 전달이 되는 그런 경로에만 대응하도록 동작이 되기 때문에
지금처럼 여러 개의 id가 연달아 전달되는 경로에는 대응 불가
위와 같이 여러 개의 id가 연달아 전달되는 경로에도 대응하는 범용적인 페이지를 만들고 싶다면,
[...id].tsx
...은 book이라는 경로 뒤에 여러 개의 id가 연달아 들어올 수 있고, 그러한 모든 id에 다 대응하겠다는 뜻
Next.js에서는 이런 식으로 설정되어 있는 경로를,
"모든 경로를 다 잡아채겠다"라는 뜻에서
캐치 올 세그먼트라고 함
세그먼트 === 구간
➡️ 구간, 경로 상에 슬래시로 구분되는 모든 구간에 대응하겠다는 의미
파일명을 [...id].tsx로 정하고,
id값을 콘솔에서 출력하면
이 id는 url로 전달된 여러 파라미터 값을 배열로 받기 때문에
오른쪽 화면처럼 확인 가능함
바로
localhost:3000/book 뒤에 아무 id값도 오지 않는 경우
이 경우 캐치 올 세그먼트도 대응이 불가능함
왜냐하면,
캐치 올 세그먼트는, 뒤에 어떤 id값이든 나와야 대응이 되기 때문
그런데 지금은 /book으로 끝났으니까 404가 리턴됨
(1) 디폴트 페이지를 만들어 대응하거나,
(2) 파일 이름을 변경하여, 완전히 범용적인 페이지로 생성
[[...id]].tsx로 설정
➡️ 옵셔널 캐치 올 세그먼트라고 함
이제 /book뒤에 경로가 들어오든 안 들어오든
모두 대응 가능한 페이지가 됨
이때 렌더링하는 페이지를 만들려면?
pages 폴더 아래에 404.tsx 파일을 만들어 페이지 역할을 할 컴포넌트를 만들면 됨
Next.js는 리액트의 확장판
➡️ 리액트의 장점이었던 CSR 방식의 빠르고 쾌적한 페이지 이동을 그대로 계씅
이번에도 Next.js에서
어떻게 하면 CSR 방식으로 페이지를 이동시킬 수 있는지 초점을 맞춰보자
이 App 컴포넌트 안에 있는 <header> 요소 안에 간단한 네비게이션 바를 만들자
(누르면 다른 페이지로 이동할 수 있는...)
HTML 코드를 작성할 때,
다른 페이지로 이동하는 링크를 만들고 싶을 땐
이렇게 <a>태그를 사용
그런데 이 <a> 태그 방식은 CSR 방식이 아니라 일반적인 방식으로,
서버에게 새로운 페이지를 매번 다시 요청하는 방식으로 페이지를 이동시키기 때문에
비교적 느린 방식으로(원하는 방식이 아님) 페이지를 이동시키게 됨
Next.js 앱에서는 a 태그가 아닌 내장 컴포넌트인 링크 컴포넌트를 이용
import문으로 컴포넌트를 가져오고,
<Link> 태그는 기본적으로 <a> 태그와 사용법이 동일
href 속성으로 이동하고자 하는 페이지의 경로를 표시
그럼 위처럼 브라우저의 CSR 방식으로 페이지를 이동시키는 링크가 잘 렌더링됨
다른 페이지로 이동하는 링크도 만들어보자
간격이 너무 없을 땐 Link 태그 사이   를 사용
이제 페이지를 이동시킬 때마다
CSR 렌더링 방식으로 쾌적하고 빠르게 페이지 이동이 제공됨
정리하자면,
<a> 태그 방식은 CSR 방식이 아니라 일반적인 방식
즉, 서버에게 새로운 페이지를 매번 다시 요청하는 방식으로 페이지 이동
= 비교적 느리기에 원하지 않는 방식이기 때문에
<a> 태그보다는 <Link> 컴포넌트를 이용!
위처럼 링크 컴포넌트를 이용해서 CSR 방식의 링크 생성 외에,
어떤 함수가 실행이 된다거나,
어떠한 이벤트가 발생했을 때에도 페이지를 이동시키는 방식이 있음
사용자가 링크를 직접 클릭했을 때 페이지를 이동시키는 방식이 아니라,
특정 버튼이 클릭되었거나, 특정 조건이 만족했을 경우 페이지 이동
버튼을 추가해서 해당 버튼을 클릭했을 때
이벤트 핸들러 안에서 페이지를 이동시키는 기능 구현
먼저 코드에 버튼을 추가하고,
(버튼을 클릭했을 때의 이벤트가 없어서 페이지를 이동하진 않음)
버튼이 클릭되었을 때 동작할 이벤트 핸들러 생성
이제 버튼을 클릭하면,
onClickButton 함수가 실행되고
라우터 객체의 push 메서드가 실행됨
그럼 인수로 전달받은 경로로
페이지를 CSR 방식으로 이동
이 방식으로
컴포넌트 내부에서 특정 조건을 만족했다거나
useEffect를 통해 어떠한 상황을 가정했을 때,
함수 내부에서도 페이지를 CSR 방식으로 이동시킬 수 있음!
그외에
라우터 객체에는 push 말고도
뒤로 가기를 방지하면서 페이지를 이동시키는 replace나,
뒤로 가기를 시키는 back 같은 메서드도 있음
즉, 사전에 불러온다는 뜻인데,
바로 페이지를 사전에 불러온다!
Next.js의 프리페칭은 사용자가 현재 보고 있는 페이지에서,
링크를 통해 이동할 수 있는,
현재 연결되어 있는 모든 페이지를 사전에 미리 불러와 놓는 기능임
➡️ 현재 사용자가 보고 있는 페이지에서, 이동할 가능성이 있는 모든 페이지를 미리 불러놓는 기능
‼️ Next.js가 이런 기능을 기본적으로 제공하는 이유는
사용자들이 다른 페이지로 이동하기 위해서 이런 웹 페이지 내부의 링크를 클릭하기 전에
현재 페이지에서 이동이 가능한 모든 페이지들에 필요한 데이터를 미리 불러와 놓음으로써
페이지 이동을 매우 빠른 속도로 지체 없이 처리하기 위함
생각해보면,
이 프리페칭인란 기능은
Next.js에서는 초기 접속 요청이 발생했을 때,
서버가 브라우저에게 사전렌더링된 HTML 페이지를 응답한 이후에
모든 자바스크립트 코드를 번들 형태로 전달
초기 접속 요청이 종료된 이후에 발생하는 페이지 이동은
서버에게 별도의 추가적인 요청 없이
브라우저 측에서 직접 자바스크립트 코드를 실행시켜서,
리액트 앱을 직접 실행하여 필요한 컴포넌트를 교체하는 방식으로,
브라우저가 클라이언트 사이드 렌더링 방식으로 처리함
그러니까, 페이지 이동을 하더라도 서버에게 추가적인 리소스를 요청할 필요가 없는데
왜 프리페칭 같은 기능이 필요한 걸까?
프리페칭은 빠른 페이지 이동을 위해 제공되는 기능임!
그래서,
이미 초기 접속 요청이 완료가 되어서 페이지가 렌더링 되었는데
왜 그 상태에서 다른 페이지로 이동하기 위해 추가적인 데이터를 왜 또 불러와야 하냐면,
Next.js는 우리가 작성해둔 모든 리액트 컴포넌트를 자동으로 페이지별로 분리해서 저장을 미리 해두기 때문
사실은 사전렌더링 과정에서 자바스크립트 번들 파일을 전달할 때,
모든 페이지에 필요한 자바스크립트 코드가 전달되는 게 아니고,
현재 페이지에 해당하는 자바스크립트 코드만 전달됨
예를 들어 /search라는 경로로 search 페이지로 접속 요청을 보냈다면
다시 전달되는 자바스크립트 번들 파일에는 search 페이지에 해당하는 코드들만 전달됨
왜냐하면,
전달되는 자바스크립트 코드의 양을 줄이기 위해서!
항상 초기 접속 요청이 있을 때마다
모든 페이지에 해당하는 자바스크립트 코드를 매번 다 번들링해서 전달하게 되면,
한 방에 전달하는 파일의 용량이 너무 커지게 됨
➡️ 전달되는 용량이 커지기 때문에 다운로드 받는 속도도, 브라우저에서 느려짐
또한, 자바스크립트 코드를 실행해서
브라우저에 렌더링 되어 있는 HTML과 연결돠는 하이드레이션 과정도 오래 걸리고
결국 앱에 상호작용할 수 있게 되는 시간인 TTI도 최종적으로 늦어짐
따라서 Next.js는 경제적으로 이 문제를 해결하기 위해서
사용자가 현재 접속을 요청한 페이지에 해당하는 자바스크립트 코드들만 따로따로 보내주게 되는 것
(처음에 봤던 사전렌더링 방식)
그러면 초기 접속 이후로 발생하는 페이지 이동은 CSR 방식으로 추가적인 요청없이 바로 처리되는 게 아님!
페이지로 이동하게 되면,
초기 접속한 페이지에서 ➡️ 다른 페이지로 이동하게 될 텐데
Next.js는 초기 접속 시 현재 접속 요청한 페이지에 해당하는 자바스크립트 코드만 보내준다고 했으니,
매번 자바스크립트 코드를 추가로 불러와야 하는 과정이 필요
그런데 이게 하이드레이션은 빨라질 수 있지만 페이지 이동은 느려지고 비효율적인 것!
➡️ 그래서 프리페칭 등장
다시,
프리페칭이 뭐냐면,
현재 사용자가 보고 있는 웹 페이지에서
링크가 존재한다든가
아니면 버튼이 존재해서 이동할 수 있는 가능성이 있는 모든 페이지의
자바스크립트 코드를 미리 불러와놓는 과정
위 단점을,
아래처럼 해결!
초기 접속이 완료된 이후 곧바로 페이지 이동이 이루어지기 전에
프리페칭이 발생해서 현재 페이지와 연결된,
현재 페이지에서 이동할 수 있는 모든 페이지들의 자바스크립트 코드를 미리 불러와 놓기 때문에
페이지를 이동할 때는 추가적인 데이터를 서버에게 요청할 필요가 없어서
기존처럼 CSR방식의 장점대로 굉장히 빠른 속도로 페이지 이동이 가능해짐
즉, 페이지를 이동할 때마다 자바스크립트 코드를 서버에서 불러와야 했던 단점을,
페이지 이동이 발생하기 전에 미리 부르니까 정작 페이지를 이동할 때는 빠르다!
결국 이런 방식으로 동작한다고 소개했던 Next.js의 사전 렌더링은,
초기 접속 요청 시에 모든 페이지에 대한 자바스크립트 파일이 다 전달되는 건 아니었고,
현재 접속 요청이 발생한 페이지에 해당하는 자바스크립트 번들 파일만 전달되고,
그 이후 페이지 이동을 빠르게 제공하기 위해서,
페이지에 접속한 이후에는 프리페칭이라는 기능을 통해,
현재 페이지에서 이동할 수 있는 페이지에 대한 자바스크립트 코드를 사전에 미리 불러와 놓게 된다고 정리
그리고 이를 통해
초기 접속 요청 시 하이드레이션 빠르게 처리하면서,
프리페칭을 통해 이후 페이지 이동까지 빠르게 처리
npm run dev 처럼 개발 모드로 가동하면 프리페칭이 동작하지 않음
매번 페이지마다 필요한 자바스크립트 코드를 서버로부터 매번 불러오기 때문
따라서 빌드에서 실행하는 프로덕션 모드로 실행
npm run build
빌드 결과가 페이지별로 출력
자바스크립트 번들의 용량이 출력되고,
스플릿팅 되는 것까지 확인(라우트 별로 표시: (/, /404, /book/[[...id]], /search, /test))
npm run start
프로덕션 모드로 실행
헤더로 인해
search 페이지와 book 페이지는 프리페칭이 완료된 상태라서
search 페이지로 이동해도 네트워크 탭에서는 아무 요청도 추가적으로 요청하지 않음
가끔 페이지에 대한 자바스크립트 코드를 다시 불러오는 네트워크 로그가 뜨긴 하는데
캐시가 만료되어서 다시 불러온 거고
기본적인 동작은 그대로 진행됨
단, 예외가 하나 있음
index 페이지에서
[/test 페이지로 이동] 버튼을 클릭하면,
맨 마지막, 번들을 추가로 불러옴
➡️ 이 /test 페이지는 프리페칭이 이루어져 있지 않은 거임
App 컴포넌트를 보면
프리페칭이 이루어진 search 페이지와 book 페이지는 Link 컴포넌트로 구현되어 있지만
test 페이지는 프로그래매틱하게 페이지를 이동하도록 설정해두었기 때문
결론적으로, Link 컴포넌트로 명시된 경로가 아니라면,
프리페칭이 일어나지 않음
만약 /test에도 프리페칭 시켜주고 싶으면
App 컴포넌트가 마운트되었을 때, 즉 처음 화면에 그려지게 되었을 때
router 객체의 특정 메서드를 통해 직접 프로그래매틱하게 이 test 페이지를 프리페칭하도록 코드 작성
컴포넌트 내부에서 useEffect호출
마운트 되었을 때만이니까, 의존성 배열은 비워두고
그 다음 콜백함수 내에 router 객체의 prefetch 라는 메서드 호출
인수로는 어떤 페이지를 프리페칭할 건지 넣어주기
이제 App 컴포넌트가 화면에 마운트 되고 나서,
곧바로 prefetch 함수가 실행되어서
테스트 페이지이에 대한 프리페칭이 실행될 것!
프로젝트를 재시작하면 반영됨
이렇게 프로그래매틱하게 이동하는 페이지도 프리페칭을 시키고 싶다면
이렇게 라우터 객체의 프리페치 메서드를 통해 특정 페이지를 명시적으로 프리페칭하도록 설정 가능
Link 컴포넌트는 자동으로 프리페칭이 적용
잘 접속할 것 같지 않으면 프리페칭하지 않게 설정
그러면 index 페이지가 열려도 search 페이지에 필요한 자바스크립트 코드는
프리페칭되지 않음
(마우스 올리면 프리페칭된다!)
Next.js 앱에서 API를 구축할 수 있게 하는 기능
이 기능을 이용하면
마치 백엔드 API 서버가 하는 일을 동일하게 간단한 API를 구축해서
클라이언트, 즉 브라우저로부터 요청을 받아 데이터베이스에서 데이터를 꺼내온다든가
아니면 또 다른 서드파티에 데이터를 불러와서 전달을 한다든가,
하는 일련의 동작을 직접 만들어볼 수 있음
npm run dev
개발모드로 실행
이 코드는 브라우저 화면을 만드는 코드가 아니라,
서버에서 실행되는 API 엔드포인트를 만드는 코드
위와 같은 경로의 파일은,
웹 페이지가 아닌 API 라우트로서 API 응답을 정의하는 파일로 자동적으로 설정이 되고
API의 경로는 이 폴더 구조에 맞춰 /api/hello란 경로를 가짐
브라우저를 통해 이런 경로로 요청하게 되면,
해당 파일 안에 써져 있는 디폴트로 보내지는 함수가 실행이 되며,
API가 작동
두 개의 매개변수를 통해
request와 response에 대한 정보를 받고 있고
{
res.status(200).json({ name: "John Doe" });
}
이 API를 호출하면 서버가
아래와 같은 응답을 줌
{
"name": "John Doe"
}
또한 status 메서드를 통해서 상태 코드를 200번으로 설정
이렇게 응답받는 것도 확인
이렇게 Next.js에서는 pages라는 폴더 아래에 api라는 폴더를 만들고
해당 폴더 안에 새로운 파일을 배치시켜주면
그 파일들은 API 라우트로서 웹 페이지를 정의하는 파일이 아닌,
API 응답을 정의하는 모드로 설정이 됨
현재 시간을 반환하는 API를 만들어 보자
위처럼,
요청과 응답 타입을 import하고,
매개변수로 넣고
현재 시간을 보관하는 date 객체를 만들어 응답하도록 명시
time이라는 api가 호출되면, 이 핸들러 함수가 실행되고
핸들러 함수에서 새로운 date 객체를 만들고
response의 응답 값으로 json 객체를 응답하도록 솔정됨
이제 브라우저에서 요청을 보내면
현재 시간이 locale 문자열로 변환되어 잘 들어옴
이 기능이 자주 사용되진 않음(..)
Next.js의 스타일링 설정 방법
Next.js 앱의 스타일링은,
사실상 리액트의 컴포넌트 스타일을 설정하는 과정과 동일함
스타일링 할 거니까
여기에 css 파일을 넣어줌
응, 안 돼~
뭐가 문제냐면 Global CSS 파일은 import할 수 없음
➡️ 제한하는 이유: 다른 페이지에 작성된 CSS 코드와 충돌날 수 있기 때문에
이렇게 css 파일을 만들어 놓으면,
➡️ index 페이지에 접속했다가(이미 index.css가 한 번 로딩됨)
test 페이지로 이동했을 때
이 index.css과 test.css 파일이 브라우저에 함께 로딩됨
즉, 어떤 순간에는 브라우저가
/* index.css */
.h1 { color: red; }
/* test.css */
.h1 { color: blue; }
이런 식으로 둘 다 가지고 있는 상태가 되고,
브라우저는 "같은 .h1 클래스에 색이 두 개네?" 하고 보게 됨
이 클래스네임을 페이지별로
겹치지 않게 잘 분할해서 사용할 수 있다면 좋겠지만,
현실적으로 다뤄야 하는 프로젝트의 규모가 커지면 커질수록
스타일 파일의 개수가 늘어나고
그에 대한 클래스네임도 많아지니
문제가 발생할 가능성이 아주 좋아짐
이러한 문제를 애초 원천 차단하기 위해,
별도의 페이지 파일이나 어떠한 컴포넌트 파일에서 별도로 css파일을 그대로 import하는 걸 제한
App 컴포넌트에서만 예외적으로 css 파일을 불러오는 걸 허용함
다시 말해, App 컴포넌트가 아니면, css 파일을 그대로 불러오는 건 불가능
난 소소하게 index 페이지에 소소하고 작은 스타일 하나를 설정하고 싶은데
import 자체가 불가능하면?
➡️ Next.js에서 기본적으로 제공하는 css 모듈이란 기능을 활용하면됨
기존의 css파일을 마치 모듈처럼 사용하도록 도와주는 기술
: css 파일에 작성해둔 클래스 네임이 중복되지 않도록 클래스 네임을 자동으로 유니크한 이름으로 파일마다 변환해줌
index.css 파일의 이름을
➡️ index.module.css 파일로 바꾸자
이름을 위와 바꾸고,
이렇게 style 되어 있던 코드를
인라인 스타일을 지우고 클래스 네임(className) 설정
그러면 css파일에 설정했던,
h1에 설정된 color: red라는 스타일이
태그에 자동으로 적용됨
그러면서도 className이 다른 파일과 겹치지 않게 유니크하게 설정됨
유니크한 클래스 네임을 더 살펴 보면,
<h2> 태그를 하나 추가하고,
css 파일에서 <h2>의 스타일을 설정하고,
다시 컴포넌트에서 <h2>의 스타일을 적용
브라우저에서 확인하면,
페이지별로 클래스 네임이 겹쳐서 발생할 수 있는 문제를 해결하기 위해
유니크한 클래스 네임으로 변환
모든 페이지에 다 적용되는 글로벌 레이아웃을 설정하는 방법 확인
이 서비스의 글로벌 레이아웃을 우리의 앱에 설정해보자
글로벌 레이아웃을 적용하려면 어떤 파일을 수정해야 할까?
➡️ Next.js 앱의 모든 페이지의 부모 컴포넌트 역할을 하는 루트 컴포넌트인 App 컴포넌트의 레이아웃을 적용
일단 기존 코드 다 지우고 header와 footer만 둔 상태
이렇게 App 컴포넌트에 header와 footer를 작성하면
모든 페이지가 header, main, footer 영역이 생김
이렇게 모든 페이지에 다 적용이 되어야 하는 글로벌 레이아웃을 적용할 때는
루트 컴포넌트인 App 컴포넌트에 이렇게 페이지 컴포넌트를 포함하는 구조로 만들어야 함
그런데 App 컴포넌트 안에 레이아웃을 구성하는 코드가 너무 길어지면,
가독성이 떨어지기 때문에
보통은 글로벌 레이아웃을 위한 별도의 컴포넌트 파일로 만들어 코드 분리
먼저, src 폴더 안에 components 폴더를 만듦
페이지 역할을 하는 컴포넌트는 children을 명시해서,
이 컴포넌트를 children으로 리턴하도록
(이때 props는 객체 타입이니까, 내부 타입은 ReactNode란 타입을 갖는다고 정의)
App 컴포넌트 내부의 코드를 제거하고,
방금 만든 글로벌 레이아웃 컴포넌트를 불러와서, 페이지 컴포넌트를 감싸서 children으로 넘겨줌
헤더 안에 링크 연결
푸터는 텍스트 명시
먼저 전체 백그라운드를 회색 톤으로 바꾸기 위해서 globals.css 수정
위 파일을 만들고,
해당 파일을 레이아웃 컴포넌트에 import
이제 스타일링 후 레이아웃 컴포넌트에 적용해보자
css 파일에 스타일링 적용 후
컴포넌트에 적용
위처럼 메인 컨테이너와 바깥 영역에 적용된 것 확인
추가로,
컨테이너 경계선에 그림자를 주고
내부 여백도 좀 추가
스타일링을 추가로 하면,
header 태그에 적용
적용은 되었으나 링크 스타일이 기본적으로 적용되어 있음
(링크 태그로 만들어졌기 때문에)
css 파일에서 header 요소에
a 태그의 스타일링을 해줌
main과 footer의 스타일
컴포넌트에 적용
각각의 페이지에 필요한 특수한 레이아웃을 개별 페이지별로 적용하기
npm run dev
완성된 페이지에 있는 서치바는,
index 페이지 말고 search 페이지에도 공통으로 존재
그러나 도서 아이템을 클릭해서 접속하는 book 페이지에는
서치바가 존재하지 않음
서치바가 존재하는 페이지를 위한 별도의 레이아웃 컴포넌트를 만들자
components 폴더 아래에 해당 파일을 만들고,
임시로 활용할 레이아웃 컴포넌트 작성
children이란 props를 받고, 페이지 컴포넌트 전달
여기에 서치바 역할을 할 요소도 만듦
이제 이 레이아웃을 index 페이지와 search 페이지에 각각 적용
어떻게 해야 할까?
우선
App 컴포넌트 안에 글로벌 레이아웃 밑에 Searchable 레이아웃을 넣자
이렇게 하면?
➡️ 모든 페이지에 공통적으로 서치 바가 나타남
즉, 적용하지 않으려 했던 book 페이지에도 서치 바가 나타남
따라서 App 컴포넌트에 중첩하는 식으로는 설정 불가능
일단 App 컴포넌트는 원복해주고
해당 레이아웃(ex. 임시 서치바)이 적용되기 원하는,
예를 들면 index.tsx 같은 페이지에
이 페이지 역할을 하는 컴포넌트인 Home 컴포넌트에 레이아웃 추가
이 메서드가 페이지 컴포넌트를, page라는 이름의 매개변수로 받아서
방금 만든 레이아웃 SearchableLayout으로 감싼 형태로 return
매개변수 page는 리액트 컴포넌트이므로 ReactNode
이렇게 getLayout 함수를 추가하면,
이 getLayout 함수는 App 컴포넌트에서 사용되고,
index 페이지에 서치바를 적용
index 페이지 컴포넌트인 Home은 함수인데 메서드를 추가하는가?
자바스크립트의 모든 함수는 사실 다 객체임
따라서 메서드가 추가 가능
함수와 객체 (getLayout 관련) 보충자료
https://reactjs.winterlood.com/0f33b159-6b19-433b-8db4-68d6b4a122e0
이제 이 getLayout을 App 컴포넌트에 불러와야 함
이때부터 어렵기 때문에 두 눈 똑바로 떠야 함👀
페이지 라우터에서 각 페이지는 App 컴포넌트를 통해 렌더링된다고 했음
App 컴포넌트의 Component는 현재 렌더링할 페이지이고,
pageProps는 그 페이지에 전달되는 props
따라서, index 페이지의 경우에도
App 컴포넌트 props인 Component로 index 페이지 컴포넌트(Home 컴포넌트)가 전달됨
이때 getLayout 변수에 앞서 index 페이지 컴포넌트에 메서드로 추가한 getLayout이 저장됨
이 getLayout이 잘 저장되었는지 브라우저 콘솔에 출력해보면
가 나오는데,
클릭해보면 어떤 함수인지 알 수 있음
우리가 만든 메서드로 잘 나오고 있음
getLayout에 우리가 만든 서치바 레이아웃이 저장되니,
_app.tsx에서 이 메서드를 사용하면 index 페이지에 서치바 레이아웃이 적용됨
getLayout이라는 메서드는,
인수로 페이지 역할을 하는 컴포넌트를 전달받아서,
그 결과값으로 SearchableLayout이 적용된 새로운 컴포넌트를 리턴하도록 만들어져 있으니까,
위처럼 getLayout이라는 함수에 의해 현재 페이지 역할을 하는 컴포넌트가,
SearchableLayout으로 감싸진 형태로 렌더링될 것
이제 서치바가 적용된 index 페이지가 렌더링됨
정리하자면,
App 컴포넌트에서는 어떤 페이지로 접속 요청이 오든간에
일단 이 App 컴포넌트가 루트 컴포넌트로서 렌더링그렇기 때문에 이 App 컴포넌트는,
현재 접속 요청이 온 페이지 역할을 하는 컴포넌트(index 페이지)로 접속 요청이 왔다면,
index.tsx의 이 Home 컴포넌트를,Component라는 props로 전달받음
이 Component(페이지 역할을 하는 컴포넌트)함수와 동시에 객체이므로
메서드로 추가 가능
➡️ getLayout으로 추가하고, 이를 가져와
중괄호와 함께 호출 시 별도의 레이아웃 적용 가능
이와 같이 페이지 컴포넌트의 메서드를 추가하고,
App 컴포넌트에서 그 메서드를 읽으면 개별적으로 레이아웃이 적용 가능
근데, getLayout 메서드가 적용되지 않은 페이지로 접속하면 문제가 발생함
book 페이지로 접속해보자
왜냐하면,
book 페이지에는 getLayout이라는 메서드를 추가한 적이 없기 때문에
해당 페이지에는 별도로 메서드를 추가한 적이 없는데,
App 컴포넌트에서는 getLayout을 저장해서 호출하고 있기 때문에,
에러가 발생하는 것
getLayout에 undefined가 저장되어서, 이를 호출하려고 하니까 오류가 발생하게 됨
이렇게 getLayout이란 메서드가 추가되지 않은 경우에는
별도의 레이아웃이 적용되지 않도록 예외 처리 필요
const getLayout = Component.getLayout ?? ((page: ReactNode) => page);
// 앞에 있는 Component.getLayout이 undefined일 때에는,
// 이 getLayout이라는 변수의 페이지를 매개변수로 받아서 그대로 리턴
?? 연산자를 통해 undefined일 땐 getLayout에 page라는 매개변수를 받아 그냥 리턴
➡️ 현재의 페이지 그냥 그대로 return
예외처리까지 완료
"너님이 지금 컴포넌트에서 getLayout메서드를 꺼내서 쓰고 있는데,
나는 그 메서드를 모른다."란 뜻
당연함. 우리가 임의로 만들어둔 메서드임
➡️ 기본적인 타입 정보에는 포함이 되어 있지 않음
따라서,
이 컴포넌트의 타입에 getLayout이라는 메서드가 존재할 거라고,
타입 정보를 추가해주면 됨
➡️ 존재하는 요소라고 판단
타입 별칭으로 type NextPageWithLayout 별도의 타입 생성
원래 Next에서 제공하는 페이지 컴포넌트의 기본 타입인 NextPage를 설정해주고,
교집합으로 이 NextPage라는 기본 타입에다 getLayout 추가
그래서 getLayout 메서드의 타입 정보로 page라는 매개 변수를 받는데,
ReactNode 타입이고,
반환값로 똑같은 ReactNode 페이지 반환
이렇게 만든 NextPageWithLayout이란 타입은
기존의 넥스트에서 제공하는 페이지 컴포넌트 타입 NextPage
&
getLayout이라는 타입의 메서드가 추가된 타입으로 정의
따라서 Props에 이 타입을 정의
export default function App({
Component,
pageProps,
}: AppProps & { Component: NextPageWithLayout }) {}
빨간 줄을 해결했음!
input태그에 사용자가 검색어를 입력하고,
검색 버튼 클릭 시 페이지 이동
먼저 css 파일을 생성
tsx 파일에 import
각 태그에 className 명시
css 파일 작성
완성!