Page Router 핵심 정리 (2)

ANN·2026년 4월 14일

OneBiteNext

목록 보기
3/3
post-thumbnail

완강 후 정리하려니, 여기가 어느 부분인지...

한 입 크기로 잘라먹는 Next.js

📌 사전 렌더링과 데이터 페칭

지난 시간까지는 목데이터를 통해 데이터를 렌더링했음

이번엔 실제 데이터를 가져오자!
서버로부터 실제 데이터를 불러오는 데이터 페칭 기능을 살펴볼 것

그 전에, 넥스트 이전, 그러니까 기존 리액트에서는 어떻게 백엔드 서버로부터 데이터를 페칭했는가?

☑️ 리액트에서의 데이터 페칭

먼저 페이지 역할을 하는 컴포넌트를 만듦


(1) 불러온 데이터를 보관할 state 생성

이 컴포넌트 내부에 서버로부터 불러올 데이터를 저장하기 위한 state를 하나 만듦

(2) 데이터 페칭 함수

fetchDate라는 함수를 만들어서,
해당 함수 내에서 fetch 메서드를 사용해서 서버로부터 데이터를 불러온 다음
그 데이터를 setState 메서드를 통해 현재의 state 값으로 업데이트

(3) 컴포넌트 마운트 시점에 fetchData 호출

데이터 페칭 함수까지 만들었다면,
컴포넌트가 마운트 되었을 때 useEffect를 호출해서
딱 1번만 위에서 만든 fetchData를 호출
➡️ 데이터를 페칭한 다음 state에 보관할 수 있게 설정

(4) 데이터 로딩 중일 때의 예외 처리

데이터 페칭이 완료되지 않았을 때의 예외처리를 위해
조건문을 통해 컴포넌트가 로딩 중임을 표시

정리하자면,

  1. 불러온 데이터를 보관할 state 생성
  2. 데이터 페칭 함수 생성
  3. 컴포넌트 마운트 시점에 fetchData 호출
  4. 데이터 로딩 중일 때의 예외 처리

와 같은 프로세스에는 단점이 있음

초기 접속 요청부터 백엔드 서버로부터 불러온 데이터가 로딩되기까지 오래 걸림

WHY?
백엔드 서버에게 보내는 데이터 요청이, 컴포넌트가 마운트 된 이후에나 발생
즉, 데이터 요청 자체가 늦게 시작하기 때문에 데이터를 불러오는 속도도 늦어짐


CSR(클라이언트 사이드 렌더링)은 브라우저가 렌더링을 직접 처리
그러니까 실제로 화면에 그려지기까지,
HTML이 렌더링도 하고, 자바스크립트도 직접 실행하느라 FCP가 늦어진다고 했음


위처럼 컴포넌트가 마운트된 이후에서야,
백엔드 서버에게 데이터를 요청하도록 코드를 작성하면,
느린 FCP를 거치고 나서야 백엔드 서버에게 데이터를 요청하기 때문에
데이터의 로딩이 완료되기까지 추가적인 시간이 더 소요됨

FCP 이후에 또 한 번 데이터를 기다려야 하는 시간이 추가되기 때문에
안 그래도 화면도 늦게 나타나는데,
데이터까지 기다리는 불편함이 초래


하지만 넥스트는 느린 FCP 문제를 해결하기 위해 사전 렌더링이란 방식으로 동작

☑️ 넥스트에서의 데이터 페칭

완성된 HTML을 바로 사용자에게 보여주므로 리액트의 느린 FCP 문제 해결


이렇게 사전 렌더링을 진행하는 과정에서


백엔드 서버로부터 현재 페이지에서 필요한 데이터를 미리 불러오도록 설정


그래서 훨씬 빠른 타이밍에 백엔드 서버로 데이터를 요청하고, 응답받을 수 있고

이로 인해 브라우저에 전달하는 HTML파일에
이미 백엔드 서버로부터 불러온 데이터가 다 포함되어 있음

➡️ 추가적인 로딩 없이 한 번에 보여주기 가능

☑️ 리액트 vs 넥스트

결국결국결국

넥스트가 더 빠르게 렌더링된다는 말씀!

데이터를 요청하는 시점이 확연히 다름!

☑️ 넥스트의 사전 렌더링

넥스트에서는 접속 요청과 동시에 백엔드에 데이터를 요청한다고 했음

그런데 만약 이 데이터 용량이 너무 크거나
백엔드 서버의 상태가 너무 좋지 않아서 응답이 오래 걸리면
유저는 어떻겠음?

당연히 사전 렌더링이 완료되기 전까지 꽤 오랜 시간 기다려야 함


그런데 넥스트는 이런 식으로 오래 걸릴 것으로 예상되는 페이지의 경우

특별히 빌드 타임에
그러니까 빌드하는 시간에 사전 렌더링을 미리 하도록 설정 가능


이처럼 여러 사전 렌더링 방식이 있음

하나하나 알아보자


📌 SSR 1. 소개 및 실습

서버 사이드 렌더링 (SSR, Server Side Rendering)

  • 가장 기본적인 사전 렌더링 방식
  • 요청이 들어올 때마다 사전 렌더링을 진행함

가장 기본적인 사전 렌더링을,
넥스트에서는 SSR이라고 함


pages 폴더 아래에 index.tsx 파일 아래에서
이제 이 페이지를 SSR 동식으로 동작하도록 해보자

☑️ getServerSideProps 함수

이 함수를 선언하면, 이제 이 인덱스 페이지는 SSR 방식으로 사전 렌더링이 이루어짐


브라우저에서 localhost:3000 경로로 인덱스 페이지를 요청하여,
넥스트 서버가 사전 렌더링을 할 때,

이 페이지 컴포넌트보다 먼저 실행되어서
인덱스 페이지 컴포넌트에 필요한 데이터를 백엔드 서버로부터 불러온다든가 하는 기능

(백엔드, 서드 파티로부터 데이터를 불러옴)

그리고 페이지 컴포넌트 실행


(이미 코드에 전부 다 작성되어 있어 강의 자료를 가져옴)

아무튼 이렇게 미리 데이터를 가져와 리턴해서,
이 객체를 홈 컴포넌트에 전달 가능

리턴값은
반드시 props라는 객체 프로퍼티를 포함하는 단 하나의 객체

그래야 Next.js가 이 객체를 읽어와 페이지 역할을 하는 컴포넌트에 전달 가능
일종의 프레임워크 문법


그럼 이 객체를 홈 컴포넌트(페이지 컴포넌트)에선 어떻게 받느냐
기존 리액트에서 props 받아오듯 받아오면 됨

(타입스크립트 사용 중이니 타입 오류를 해결하기 위해 any로 정의)

출력해보면,

hello가 잘 출력됨


☑️ 주의할 점

getServerSideProps 함수 실행

이 함수는 사전 렌더링을 하는 과정에서 딱 한 번만 실행이 되고,
서버 측에서만 실행됨!

그래서 이 함수에서 console.log 함수를 호출해봐야,
브라우저에선 출력되지 않음

대신 터미널에는 출력됨

(첨부한 코드는 강사님 꺼, 밑에 파란 터미널은 나의 코드(강사님 코드와 같음)로 실행한 결과)


따라서 이 함수 내에서
브라우저 환경에서만 이용할 수 있는 window.location을 통해 접근하려고 하면 오류 발생

자바스크립트의 window는 브라우저인데,
서버 환경에서만 실행되는 getServerSideProps 함수에서는 브라우저를 읽을 수 없음

따라서 window는 undefined가 되는데,
undefined에 점 표기법으로 location이란 프로퍼티를 꺼내려고 하니까 오류 발생


오류 화면을 보면 윈도우가 정의되지 않았다는 메시지가 나옴
➡️ 그래서 window.alert(), window.confirm() 같은 것도 다 안 됨


Home 컴포넌트의 실행

Home 컴포넌트 또한 사실 서버에서 먼저 실행된 다음 브라우저에서 한 번 더 실행됨

  • 브라우저로부터 접속 요청을 받았을 때 사전 렌더링을 위해 서버 측에서 한 번 Home 컴포넌트 실행
  • 브라우저에서 자바스크립트 번들 형태로 전달 되어서, 브라우저 측에서 실행될 때(즉 하이드레이션 과정이 실행될 때) 한 번 더 실행

➡️ 서버에서 한 번, 브라우저에서 한 번, 홈 컴포넌트는 총 두 번 실행됨


요점이 뭐냐면,
Home 컴포넌트도 서버에서 한 번은 실행될 테니까
따라서 Home 컴포넌트도 window 같은 객체를 사용할 수 없음

서버에서는 window가 undefined가 되어서,
window의 location, 즉 undefined를 호출하는 꼴이 되어버리기 때문에 안 됨


☑️ 그럼 브라우저에서만 실행되는 코드를 작성하려면?

여러가지 방법이 있지만, 가장 쉬운 건 useEffect

이 코드는 서버에서 실행하지 않음

조건 자체가 컴포넌트가 마운트된 이후,
그러니까 화면에 나타난 이후 실행하는 함수를 만드는 것이라서,

➡️ 서버 측에서 실행되지 않고 브라우저에서만 실행됨

☑️ getServerSideProps로부터 전달받는 props의 타입은?

대부분 타입 정의는 이미 넥스트에서 제공하고 있음

그래서 InferGetServerSidePropsType을 import하고,
제네릭으로 만든 getServerSideProps를 넣어주면,
이 타입이 방금 만든 getServerSideProps 함수의 반환값 타입을 자동으로 추론


잘 정의되었는지 확인해보기 위해서
구조분해 할당으로 받았던 매개변수 data 대신 아예 props로 바꿔주고,

마우스를 올려 타입이 어떻게 정의되었는지 보면
data는 string으로 반환값 타입이 잘 정의됨


📌 SSR 2. 실습

일단 백엔드 서버 가동


☑️ 인덱스 페이지

이 두 가지 API 호출을 담당할 함수를 만들자
두 API 모두 index.tsx에 작성

/book 모든 도서 불러오기

근데 index.tsx에 모두 작성하면 너무 복잡하니까
fetch하는 함수는 분리하자

fetch-books.ts

lib 폴더를 만들어주고
모든 도서를 불러오는 함수를 만들자


  • 비동기 함수를 하나 만들어줌
  • 서버의 주소를 작성함
  • API를 호출하는 코드를 작성하기 위해, 실패했을 수도 있으니 try문

  • fetch 메서드 호출
  • fetch 메서드가 실패할 수도 있으니,
    조건문으로 예외처리

이 조건문에 걸리지 않으면 요청이 성공일 테니까,
fetch 메서드의 응답값을 json 포맷으로 변환해서 return


catch 문은,
console.error 메서드로 에러 메시지를 출력하고
빈 배열을 반환


간단하지만 예외처리를 완료했으니
fetchBooks 함수의 반환값 타입도 정의하자면
이 함수는 서버로부터 모든 데이터를 불러와서 반환하는 함수이니 비동기로 반환

Book의 타입을 interface로 정의하고,
이 타입을

반환값 타입으로 프로미스와 제네릭으로 명시
근데 여러 개 불러올 거니까 배열로


이처럼 fetchBooks 함수 완성

index.tsx

이제 getServerSideProps에서, 위에서 만든 fetchBooks 함수를 호출

비동기 함수이니 await를 쓰면, 이 함수에도 async 키워드를 붙여야 함


이제 이 allBooks를 props로 전달하고,
Home 컴포넌트에서 잘 받는지 console.log로 출력


그럼 이렇게 서버와 브라우저에 동시에 데이터가 나옴


이렇게 받은 데이터를 map 메서드로 렌더링하자

그럼 이제 목 데이터가 아닌
디비 서버에 등록된 모든 도서가 잘 렌더링되고 있음


/book/random 랜덤(추천) 도서 불러오기

fetchRandomBook.ts

같은 방식으로 fetchRandomBook 함수를 분리하자

  • API 호출
    • 인수로는 API 주소 전달
  • 실패할 수 있으니 try-catch문
    • 조건문으로 실패 시 에러 호출해서 에러 던지고,
    • 성공 시 해당 데이터를 json 형태로 return
  • catch문은 에러를 출력하고 빈 배열 return
  • 반환값 타입은 비동기로 반환하니까 Promise
  • 제네릭으로 BookData 배열

index.tsx

이제 index 페이지로 넘어와서

방금 만든 fetchRandomBooks로 결과값을 받아와
props로 데이터를 페이지 컴포넌트에 전달


컴포넌트로 해당 데이터를 전달 받아서,
map 메서드로 렌더링


이제 새로고침할 때마다 새로운 추천도서를 불러옴
index 페이지에, 필요한

추천 도서와, 랜덤 도서를 동시에 불러와보자

지금 요청이 어떻게 이루어짐?

지금은 fetchBooks를 실행하고,
그 다음 fetchRandomBooks를 실행하는 직렬적 방식


이 모든 도서, 추천 도서 함수를 병렬로 실행해보자

위 두 함수 호출을 지우고,

const 배열로 두 데이터를 받아오고,

어디로부터 받아오냐면
Promise의 all이란 메서드를 호출한 결과로부터 받아옴

Promise.all 이라는 메서드는,
인수로 전달한 배열 안에 들어 있는 모든 비동기 함수를 동시에 실행시켜주는 메서드

그래서 인수로 배열을 넣고,
첫 번째 아이템에는 fetchBooks(),
두 번째 아이템에는 fetchRandomBooks()
를 넣으면

두 함수가 동시에 병렬로 작동하고, allBooks와 recoBooks 데이터를 동시에 불러옴


이렇게 인덱스 페이지의 index.tsx 완성


☑️ search 페이지

여기 검색하면,
이동하게 될 search 페이지에도 검색 결과 데이터를 불러올 수 있게 설정하자

search 페이지의 index.tsx

먼저 search.tsx에 getServerSideProps 함수 추가
➡️ search 페이지는 SSR 방식으로 동작


이제 이 함수 내에서 검색 결과를 백엔드 서버로부터 불러와서 컴포넌트에 전달해야 함


그러려면 브라우저를 키워 주소창을 보면

이와 같이 쿼리 스트링으로 전달된 검색어를 읽어와서
검색어에 해당하는 데이터를 백엔드 서버로부터 불러오는 기능을 만들어야 됨

➡️ 그러면 getServerSideProps에서 이 검색어에 해당하는 데이터(쿼리스트링의 값)을 읽어와야 함
(fetch를 여기서 해야 하니까)


context란 매개변수를 이용해야 함
이건 넥스트에서 제공


이 context라는 매개변수에는 현재 브라우저로부터 받은 요청에 대한 모든 정보가 다 포함되어 있음

출력해보면 이와같이 데이터가 나오는데,
이 중 query란 프로퍼티로 쿼리 스트링이 잘 전달됨

이걸 꺼내서 쓰자


이렇게 context의 query의 q를 꺼내와서 쓸 수 있음

API 요청 확인

백엔드 서버로부터 검색어에 해당하는 검색 결과를 불러와보자

스웨거를 통해서 어떤 주소로 보내야 하는지 확인해 보면,

Request URL을 확인 가능


Response 도 확인 가능


fetch-books.ts

그러니까 위 검색 API를 호출하는 새로운 함수를 만들어야 함
근데 기존 함수를 확장하자

만들었던 fetchBooks 함수에 매개변수 q를 추가하고,
전달받지 않을 수도 있으니 선택적 프로퍼티로 설정
➡️ 전달받지 않아도 됨


만약 q가 있다면
검색 결과를 불러오는 API주소로 요청하기 위해 url 수정

이때 url의 선언도 const에서 let으로 수정함
요청 url에 쿼리 스트링이 있다면, 해당 쿼리 스트링을 붙이도록 수정


인덱스 페이지에서는 q없이 이 함수를 호출하고,


서치 페이지에서는 검색어를 인수로 전달해서 검색 결과를 불러옴
인수는 string이거나, stringArray거나, undefined일 수 있으니 string으로 단언

이제 이 결과를 props 객체로 페이지 컴포넌트에 전달


페이지 컴포넌트에서는,
구조분해 할당으로 해당 객체를 받아오며
위에서 살펴 본 넥스트의 내장 타입을 활용하고 제네릭으로 typeof까지 넣어서 타입 추론

기존 mock데이터는 지워주고,
받아온 books 데이터를 map 메서드로 이용해서 렌더링


이렇게 search 페이지의 index.tsx 완성


서치 페이지의 기능이 잘 구현됨

☑️ book 페이지

book 페이지의 index.tsx

이 getServerSideProps 함수를 추가하여
북 페이지도 SSR 방식으로 동작


북 페이지는,

URL 파라미터로 불리는 값으로 전달되는
이 도서의 아이디를 기준으로 해당 도서의 데이터를 불러와야 함

  • 1 ➡️ 1번 도서 데이터
  • 13 ➡️ 13번 도서 데이터

이 함수 내에서 URL 파라미터를 불러와야 함


쿼리스트링처럼, URL파라미터를 불러오기 위해
context 사용

URL 파라미터는 context의 params라는 프로퍼티에 있음

근데 URL 파라미터가 없을 수도 있으므로
! 단언으로 값이 있을 거라고 단언

이렇게 단언해도 괜찮은 이유는
이 페이지인 [id].tsx 자체가 URL 파라미터가 하나 있어야 접근 가능한 페이지이기 때문에
이 페이지에 URL 파라미터가 아예 없다는 건 말이 되지 않기 떄문에
undefined가 아닐 거라고 단언

API 요청 확인

이 API를 사용해서 특정 도서의 데이터를 불러와보자


리퀘스트 형식 확인하고,


리스폰스 형식도 확인

fetch-one-book.ts

이제 fetch 함수를 만들자

  • 이 함수가 불러와야 하는 도서의 아이디를 매개변수 아이디로 받아오게 함
    • 이 매개변수의 타입은 number
  • API 요청 주소 명시
  • 마찬가지로 fetch 메서드를 통해서 url을 전달하고, try-catch로 예외처리
    • 근데 에러일 때 빈 배열을 반환하면 안 됨
      ➡️ null 반환
  • 반환값 타입은 비동기니까 Promise로, BookData 타입 값으로 반환하는데,
    실패할 수도 있으니까 null값으로도 반환할 수 있게 유니온 타입

다시 북 페이지로 돌아가,

context로 가져온 URL파라미터에 해당하는 id는 기본적으로 string 타입을 갖기 때문에,
인수로 전달할 때는 number로 형 변환

이제 fetch메서드를 통해서 받아온 book 데이터를 props로 전달


페이지 컴포넌트에서는 해당 book 데이터를 props로 받아서
구조분해 할당으로 각 프로퍼티에 해당하는 데이터를 가져오는데,
타입 오류가 발생!

왜냐하면 이 book이 null일 수도 있기 때문에!


그래서 예외처리를 추가하면, 타입 오류가 사라지고 북 페이지에 해당하는 데이터를 가져옴

렌더링은 기존 목 데이터 사용하던 것에서 수정할 필요가 없음


이렇게 book페이지의 index.tsx 완성


이제 서버 사이드 렌더링 방식(SSR)을 통해 모든 페이지 작동 완료


📌 SSG 1. 소개

☑️ SSR 복습

한번 응답을 마친 페이지도,
다시 요청이 들어오면
새롭게 다시 사전 렌더링을 진행

매번 백엔드 서버에 새로운 데이터를 요청


그렇게 되면 최신으로 데이터를 유지할 수 있지만,

데이터의 용량이 너무 크거나 서버 상태가 좋지 않으면
데이터 응답 속도가 너무 느려짐

만약 이 응답 속도가 10초가 소요된다면,
이거 때문에 10초 동안 꼼짝없이 브라우저 로딩을 기다려야 함
➡️ 매번 새롭게 사전 렌더링을 진행, 데이터도 매번 새롭게 불러오기 때문에 생기는 특징

☑️ SSG 정적 사이트 생성

정적 사이트 생성 (SSG, Static Site Genreation)

  • SSR의 단점을 해결하는 사전 렌더링
  • 빌드 타임에 페이지를 미리 사전렌더링 해둠

SSR이라는 방식의 치명적인 단점을 해결하는 새로운 사전 렌더링 방식
빌드 타임에 미리 페이지를 사전 렌더링을 한다는 특징이 있음


npm run build일 때
사전 렌더링을 진행해서 딱 한번만 페이지를 생성

그리고 다시는 새롭게 페이지를 생성하지 않음


빌드 타임에 만들어두었던 HTML 페이지를 응답

그러면 사용자는 매우 빠르게 완성된 화면을 봄


백엔드와의 상호작용이 필요하다고 하더라도
이 과정이 특정 상황으로 인해서 오래 걸린다고 해도

모든 상황은 서버가 가동되기 이전인 빌드 타임에만 일어나기 때문에,
빌드 타임 이후 발생하는 접속 요청에는 굉장히 빠른 속도로 이미 만들어진 페이지를 바로 응답 가능


서버로부터 받은 HTML 페이지 렌더링

이후 과정은 앞서 살펴본 SSR 방식과 동일하게 진행됨

  • 서버로부터 받은 HTML 페이지를 화면에 렌더링
  • 넥스트 서버가 JS 번들을 후속으로 전달하면 브라우저에서 이를 실행해서 하이드레이션
    ➡️ 상호작용이 가능한 페이지로 거듭남

☑️ SSG의 장단점

SSG의 장점

사전 렌더링에 많은 시간이 소요되는 페이지더라도
사용자의 요청에는 매우 빠른 속도로 응답 가능

SSG의 단점

매번 똑같은 페이지만 응답함
최신 데이터 반영은 어려움

빌드 타임 이후에는 다시는 페이지를 새로 사전 렌더링하지 않기 때문에
(즉, 페이지를 새로 생성하지 않기 때문에)

접속 요청을 보내더라도 매번 똑같은 페이지만 응답하여 최신 데이터 반영이 어려움

따라서,
데이터가 자주 업데이트되지 않는 정적인 페이지에 적합한 사전 렌더링 방식


📌 SSG 2. 정적 경로에 적용하기

서버 켜기

넥스트 서버 켜기

백엔드 서버 켜기

☑️ 인덱스 페이지

getServerSideProps 함수를 export로 내보내면, 해당 파일이 담당하는 페이지는 SSR로 담당한다고 했음
특정 페이지를 SSG 방식으로 작동시키는 방법도 비슷함

이 함수의 이름을 바꾸면 됨


이제 이 페이지는 SSG 방식으로 동작

이 함수는 사전 렌더링 과정에서 필요한 데이터를 불러와서,
불러온 데이터를
props 안에 객체 형태로 컨포넌트에 전달


이제 페이지 컴포넌트에서는
(이전에 본 SSR에서 봤던 것처럼) 전달받은 데이터를 props로 받아와서 컴포넌트 내부에서 자유롭게 사용

단, 이제 이 프롭스의 타입을 정의할 때 기존과 다름

(Before)

(After)

위와 같이 props의 타입을 바꿈

InferGetStaticPropsType은 함수의 반환값 타입을 자동으로 추론해서 props의 타입을 설정해주는 역할을 함


이제 이 인덱스 페이지는 SSG 방식으로 동작하도록 수정했으니,
이 페이지가 빌드 타임에 딱 한 번만 실행되는지 확인

사전 렌더링이 빌드 타임에만 딱 한 번 이루어질 것이라서
console.log("인덱스 페이지"); 이 함수도 딱 한 번만 실행

그리고 브라우저에게 새롭게 페이지를 요청해도 이 메시지가 두 번 출력되지 않을 것


그런데!

응, 아니야^^

지금 요청이 들어올 때마다 사전 렌더링이 진행되는데,(마치 SSR처럼!)
왜냐하면, 지금 넥스트 앱을 개발모드로 실행하고 있기 때문

개발 모드는 수정 결과가 바로바로 동작하기 때문에,
SSR이든 SSG든 계속 사전 렌더링을 하고 있음

빌드 타임이 있으려면 빌드를 해야지!


npm run build

그러면 정적 페이지 생성을 위한 데이터를 수집하고,


그 이후로 SSG로 동작하도록 설정한 페이지가 생성됨
앞서 설정한 getStaticProps 함수가 빌드 타임에 잘 실행되고 있음

저 "인덱스 페이지"가 실행되는 것으로 확인


페이지별 빌드 결과

● HTML로 사전 렌더링된 페이지

getStaticProps를 사용해서 데이터를 불러오는 페이지
인덱스 페이지에만 이 기호가 붙어 있음

○ 정적인 페이지

그냥 기본으로 설정된 SSG 페이지
SSG처럼 정적인 페이지인데 getStaticProps를 설정해두지 않은 페이지
➡️ 페이지로서 만들어 놓긴 했지만 아무 설정하지 않은 페이지

넥스트는 이렇게 아무것도 적용하지 않은 페이지를
기본값으로 정적인 페이지로 빌드 타임에 사전 렌더링하도록 설정

기본값은 SSG와 동일하게 동작

404 페이지, tst 페이지가 그 예시

f : 동적인 페이지

다이나믹 페이지 혹은 동적인 페이지라고 하며,
server-rendered on demand: 주문형
➡️ 이 페이지는 계속해서 사전 렌더링이 될 거야, 라는 식으로 이해

북 페이지, 서치 페이지 외에도
api 밑의 파일에도 SSR이 붙는데,
넥스트가 기본적으로 모든 API Routes들을 다이나믹하게
즉, SSR로 작동하도록 설정하기 때문


이제 이것을 프로덕션 모드로 실행

npm run start

이제 인덱스 페이지는 SSG 방식으로 빌드 이후 다시는 생성되지 않도록 설정해둔 페이지이므로
서버 측 로그에 아무 메시지도 출력되지 않고
getStaticProps 함수가 다시는 실행되지 않음을 확인

☑️ 서치 페이지

서치 페이지에서의 SSG도 마찬가지로 바꿔줌

먼저 getServerSideProps를 getStaticProps로 함수 이름을 바꿔줌


컨텍스트 타입도 바꿔주어야 함

이제 서치 페이지 역시 SSG 방식으로 작동할 준비가 되었는데,
지금 context 매개변수로부터 쿼리 스트링을 가져온 라인에서 타입 오류가 발생하고 있음


context 객체에 쿼리스트링을 보관하는 query라는 프로퍼티가 없다는 뜻

진짜 없음


왜냐하면 getStaticProps 함수에 전달되는
이 context라는 매개변수에는 쿼리 스트링을 포함하고 있는 쿼리 프로퍼티가 존재하지 않음

그 이유는,
getStaticProps, 그러니까 SSG 방식은 앞서 말했듯 빌드 타임에 딱 한 번만 실행
그런데 빌드 타임에 쿼리 스트링을 알 수 없음

쿼리 스트링은 브라우저에서 사용자가 직접 입력한 검색어나, 리스트의 정렬 기준 같은 게 전달되는 공간이기 때문에
결론적으로 쿼리 스트링에 어떠한 값이 들어올지는 빌드 타임에 getStaticProps 함수 안에서 알아낼 방법이 없음


따라서, 서치 페이지에서는 SSG를 쓸 수 없다!
엄밀히 말하면 검색 결과를 서버로부터 불러오는 동작을 수행할 수 없음
(쿼리 스트링을 꺼내올 수가 없어서)


그럼에도 불구하고, 이러한 페이지를 SSG 방식으로 동작하고 싶다면,
현재 스트링을 꺼내와서 해당 값을 기준으로 검색 결과를 불러오는 과정을,
사전 렌더링 과정 이후, 페이지 역할을 하는 컴포넌트에서 직접 진행

즉, 리액트 앱에서 했던 방식대로 데이터 페칭


빌드 타임 중에는 어차피 쿼리스트링을 불러올 수 없으니
getStaticProps는 아예 주석처리를 하고,


props로 받아올 것도 없으니,
페이지 컴포넌트에서는 props를 삭제

대신에 컴포넌트 내부에서 useRouter 훅을 호출해서
이 훅의 반환값에서, query 프로퍼티의 값을 꺼내 쿼리 스트링을 꺼내옴


useEffect를 사용해서 검색어인 q가 변경되고,
변경된 p에 값이 있다면(공백이 아니라면)
그 안에서 검색 결과를 불러오는 로직이 작동될 것
➡️ 이것을 state에 보관해 보자


(state 선언)
검색 결과를 저장할 state를 useState 훅을 이용해서 만듦
이 books state의 타입을 배열 타입으로 저장하게 함

(결과를 fetch로 가져오고, 이를 state에 저장)
검색 결과를 불러올 함수 fetchSearchResult는
비동기로 작성하여, fetch 함수를 호출하고
인수로는 현재 검색어 q를 스트링 타입으로 단언하여 전달
검색 결과를 데이터라는 이름으로 불러옴
결과를 state에 저장함

(검색어가 변경되면, 결과를 가져오게 함)
useEffect 안에서 검색어 q가 변경되었을 때
변경된 p에 값이 있다면(공백이 아니라면)
작성한 fetchSearchResult 함수를 호출

이제 이 서치 페이지는 getStaticProps와 getServerSideProps가 모두 없어져
기본적으로 SSG 방식으로 동작


기본적으로 SSG 방식으로 동작하지만,
쿼리 스트링으로 전달되는 검색어를 빌드 타임에는 알 수 없기 때문에
사전 렌더링 과정에서는 이 페이지의 레이아웃, 즉 div 태그 정도만 렌더링됨

그리고 컴포넌트가 마운트된 이후에
브라우저(클라이언트 사이드)측에서 컴포넌트가 다시 실행되면서,
직접 쿼리 스트링으로 검색어를 불러와서 검색 결과 데이터를 클라이언트 사이드 측에서 렌더링함


다시 빌드해보자

search 페이지가 f에서 ㅇ가 되었음


이제 npm run start로 프로덕션 모드로 가동해서
네트워크 탭을 열고 새로고침하면,

넥스트 서버 측에서 보내주는 파일을 볼 수 있는데,

그 중 가장 위에 가장 처음으로 보내주는 사전 렌더링 결과인 html 파일을 클릭해보면,

검색 결과 데이터는 제외하고 나머지 부분만 렌더링해서 브라우저에게 보내줌

이제는 클라이언트 측에서,
컴포넌트 안 쪽의 useEffect를 통해 기존 리액트 앱의 방식대로 동작함

☑️ 정리

index 페이지처럼
빌드 타임에 사전 렌더링할 때 데이터를 불러오고 싶으면 getStaticProps라는 함수를 사용하고

search 페이지처럼 쿼리 스트링을 사용해도 빌드 타임에 데이터를 불러올 수 없는 페이지는
데이터를 그냥 리액트 앱에서,
클라이언트 사이드 측에서 직접 페칭해서 불러오도록 설정하는 것도 가능


📌 SSG 3. 동적 경로에 적용하기

동적 경로를 갖는 페이지도 SSG 라는 사전 렌더링 방식으로 작동하게 해보자

먼저 이 함수를 getStaticProps로 바꿔줌
당연히 매개변수 context 타입도 GetStaticPropsContext로 바꿔야 함


이제 페이지 컴포넌트도 props의 타입을 바꿔야 함

이제 저장하고,
북 페이지 렌더링이 되는지 확인


안 돼 돌아가

다이나믹한, 동적인 경로를 갖는 SSG 페이지에는
getStaticPaths 라는 함수가 추가로 필요하다고 함

왜냐하면 이 북 페이지가 동적 경로를 갖는 페이지이기 때문


동적 경로가 뭐였더라?

URL의 book 뒤에, id라는 URL 파라미터를 포함한 여러 개의 경로를 가능하게 하는 것
즉, 현재 URL 파라미터로 전달하는 id 값마다 오른 쪽에 보이는 여러 개의 페이지를 렌더링할 수 있음

각 id마다 해당 id를 갖는 도서 페이지를 각각 렌더링함


그렇기 때문에 이 페이지를 넥스트 서버 측에서
빌드 타임에 SSG 방식으로 사전 렌더링을 통해 미리 생성해 두기 위해서는


선수 과정으로 이 페이지에 어떠한 URL 파라미터들이 존재할 수 있는지
그럼으로써 어떠한 경로가 존재할 수 있는지 설정하는 과정이 반드시 필요

그렇지 않으면 어떤 경로가 있는지 모르니 사전 렌더링 진행조차 불가

따라서, 사전 렌더링 이전에 빌드 타임에 어떤 경로들이 존재하는지 설정하는 작업을 해야 함


예를 들어, URL 파라미터의 id 값이 1, 2, 3번이 있을 수 있다고 설정하면,


그 다음 과정인 사전 렌더링 과정에서 이러한 경로에 있는 모든 페이지를 각각 생성하게 됨

그러니까 URL 파라미터로 전달되는 값이 1번, 2번, 3번이 있을 수 있다고 설정했으니
사전 렌더링 과정에서도 book/1.html, book/2.html, book/3.html 이라는
3개의 html 문서를 사전 렌더링하게 되는 것

따라서 빌드 종료 후 접속 요청 시
앞서 만들어둔 해당 페이지를 전달함


결론적으로 넥스트 앱의 북 페이지처럼
동적인 경로를 갖도록 설정이 된 페이지에 SSG를 적용시키려면
반드시 사전 렌더링이 진행되기 전에 이 페이지에 존재할 수 있는
모든 경로를 직접 설정하는 작업을 선수로 진행 필요

이때 이 역할을 하는 함수가 아까 오류 페이지에서 본 getStaticPaths라는 함수
➡️ 이 함수를 호출해서 현재 이 페이지에 존재할 수 있는 경로를 설정하고,
그렇게 설정된 경로에 해당하는 페이지를 getStaticProps 함수를 일일이 한 번씩 다 호출해서
사전에 여러 개의 페이지를 렌더링하는 방식으로 동작

☑️ 실습

book 폴더의 [id].tsx 파일 내에서,
getStaticPaths 함수를 명시하고, 해당 함수 내에서 객체를 리턴함


paths 배열

객체 안에 paths라는 값으로,
이 페이지에 어떠한 URL 파라미터가 존재할 수 있는지 배열로 반환하도록 설정


예를 들어 아까처럼, 1, 2, 3번이 있을 수 있다고 설정하려면
path 배열 안에 하나의 경로 아이템을 객체로 설정

URL 파라미터를 의미하는 params라는 값으로 id는 문자열 1

URL 파라미터로 1, 2, 3번이 들어올 수 있고,
그럼 book/1, book/2, book/3이라는 페이지가 존재할 수 있다고 설정한 것

참고로,
이때 URL 파라미터의 값은 문자열로만 명시
"1", "2",...

그래야만 넥스트가 이 경로를 정상적으로 읽을 수 있음
프레임워크 문법상 URL 파라미터의 값은 문자열로 설정


fallback 옵션

이렇게 path 값을 설정했으면 추가적으로 설정할 옵션이 있음

fallback: 대체, 대비책, 보험 등
➡️ 만약 브라우저에서 넥스트 서버에게 book/4와 같은 경로로 접속 요청하게 되면
path 값으로 설정한 URL에 해당하지 않는,
존재하지 않는 것 같은 URL 로 접속 요청하게 되면
어떻게 할 건지 대비책을 설정하는 옵션

➡️ 이 중에 위처럼 작성한,
false: 아묻따 Not Found 페이지 반환

이렇게 없는 페이지로 취급함

빌드 결과

북 페이지를 정적으로 잘 설정해둠


또한, 이러한 정적인 페이지는
.next 폴더 안에 실제로 산출물로 저장됨


사전렌더링이 되어 있음

그래서 book/1로 요청이 오면
넥스트 서버는 이 html 파일을 지체 없이 보내주어서
매우 빠르게 동작할 것!!


📌 SSG 4. Fallback 옵션 설정하기

다양한 동적 경로를 갖는 book 페이지에 SSG 방식의 사전 렌더링을 적용해주기 위해
getStaticPaths라는 함수를 작성해서,
이 함수 안에서 path라는 값을 리턴해서 현재 어떤 페이지가 존재할 수 있는지 직접 설정

위 예시 코드에 따르면 다음 3개의 페이지가 존재할 수 있음

  • book/1.html
  • book/2.html
  • book/3.html

이때 fallback이라는 옵션의 값을 false로 설정하여,
이 path에 명시하지 않은 경로의 요청이 들어오면
(즉 없는 경로로 요청 시)
자동으로 Not Found 페이지를 반환하도록 설정함


그런데 이렇게 설정하면,
나중에 새로운 도서가 등록 되었을 때
(4번이나 5번 같은 도서들이 등록되어 페이지가 새롭게 추가될 필요가 있을 때)

이 fallback 옵션에 따라
미리 설정한 paths 외에는 모두 Not Found 페이지를 반환할 것이라
정상적으로 모든 도서의 페이지를 제공하기 어렵게 될 것

이럴 때는 fallback 옵션을 다른 것으로 설정함

fallback 옵션 설정

  • false: 404 Not Found 반환
  • blocking: 즉시 생성 (Like SSR)
  • true: 즉시 생성 + 페이지만 미리 반환

☑️ blocking 옵션

없는 경로로 요청이 들어왔을 때
해당하는 페이지를 마치 SSR 방식처럼 넥스트 서버 측에서 사전 렌더링을 거쳐 생성해서 반환

이렇게 세 개의 페이지만 생성해 두었는데,


만약 브라우저에서 book/100 같은 존재하지 않는 경로로 요청을 보내면

false 옵션은 Not Found를 반환했음


blocking은 SSR 방식처럼 실시간으로 요청받은 페이지를 사전 렌더링해서 브라우저에게 반환

그렇기 때문에 이 옵션을 사용하면 빌드 타임에 사전에 생성해두지 않은 페이지도 사용자에게 제공 가능

실습


빌드

npm run build

여전히 3개의 페이지만 사전 렌더링 됐지만,

npm run start

프로덕션 모드 가동 시

4번 페이지는 paths에 없었지만 실시간으로 생성되는 것 확인 가능


4번 페이지도 정적 페이지로 잘 생성되어서 .next 에 저장됨


이렇게 빌드 타임 이후에 생성된 페이지들은
처음 요청할 때는 즉각적으로 생성이 되어야 해서
SSR 방식으로 동작해서 비교적 느리게 페이지가 렌더링되지만

한 번만 만들어두면
자동으로 넥스트 서버에 저장이 되어서
그 이후의 요청에는 페이지를 새로 요청할 필요가 없음

그래서 새로고침하면 매우 빠른 속도로 화면에 렌더링 됨


이 방식은 SSR + SSG 가 결합된 형태고,
이렇게 동작하는 이유는 fallback 옵션이 blocking이기 때문임

그래서 우리가 추후 도서의 상세 페이지 같은 동적인 페이지를 구현할 때,
빌드 타임에 모든 도서의 id를 불러오기 어려운 상황이라면

그때는 fallback의 blocking이라는 옵션을 이용해서,
첫 번째 요청에는 SSR 방식으로 페이지를 새로 생성하여 신규 데이터를 반영하도록 하고,
그 이후 요청에는 SSG 방식으로 저장된 페이지를 매우 빠르게 반환하도록 할 수 있음

blocking 방식의 주의사항

존재하지 않았던 페이지를 SSR 방식으로 새롭게 생성할 때
백엔드 서버에 추가적인 데이터를 요청해야 한다고 해서,

페이지의 생성 시간, 그러니까 사전 렌더링하는 시간이 길어지면,

브라우저에 넥스트 서버가 아무것도 응답하지 않아서
어쩔 수 없이 로딩이 발생

페이지의 크기에 따라 꽤 오랜 시간을 기다려야 할 수도 있음

☑️ true 옵션

위 blocking 옵션의 주의사항을 해결하기 위해
fallback의 세 번째 옵션인 true를 활용


그러면 브라우저로부터 존재하지 않는 페이지를 요청받았을 때

일단 props가 없는 페이지를 빠르게 생성해서
즉시 브라우저에게 지체 없이 반환

이 props는 페이지에 필요한 데이터를 계산하는
getStaticProps 함수가 페이지 컴포넌트에게 전달하는 페이지에 필요한 데이터를 말하는 것임
(페이지 컴포넌트에서 props로 받잖아요잉)

그래서 백엔드 서버로부터 불러온 도서 데이터 같은 것들이
방금 말한 props에 포함되는데,

이 props가 없는 페이지라고 하면
그 데이터가 없는 페이지임

그래서 데이터를 백엔드 서버로부터 불러오는 복잡하고 오래 걸리는 과정은 생략하고
일단 컴포넌트가 렌더링하는 레이아웃만 렌더링하기 위해 props가 없는 페이지를 사전 렌더링해서 반환


그 다음 이 페이지에 필요한 데이터인 props만 따로 계산해서,
계산이 완료되면 그때 브라우저에게 후속으로 데이터만 따로 보내주게 됨


브라우저 입장에서는 일단 데이터가 없는 버전의 페이지를 만들어서
화면에 렌더링 해두었다가
(이때 로딩 바를 보여준다든지)

나중에 서버가 props 계산을 마쳐 props값만 따로 전달하면
데이터가 들어오는 것이니까,
이 데이터를 포함한 페이지를 화면에 뒤늦게 렌더링

이렇게 하면
빌드 타임에 생성해 놓지 않은 페이지를 fallback blocking 옵션처럼 그대로 제공하면서도
동시에 사용자에게 긴 로딩 시간 대신에 데이터가 없는 버전의 페이지라도 일단 먼저 보여줌

실습

fallback 옵션을 true로 변경


이때 paths로 설정하지 않는 경로로 접속 요청이 들어오면
props가 존재하지 않는 상태의 페이지를 먼저 사전 렌더링해서 브라우저에게 보내주니까,

데이터를 가져오는 부분은 생략하고,

페이지 컴포넌트만 빠르게 사전 렌더링해서 보내주는 것임


그리고 넥스트 서버에서 뒤늦게

이 함수를 실행하고, 데이터를 계산해서
후속으로 브라우저에게 book과 같은 데이터를 따로 전달함

빌드 및 확인

먼저 빌드 npm run build

그리고 .next 폴더를 확인해서
빌드 타임에 생성된 페이지 확인

우선 코드 상 paths에 설정한 대로 1, 2, 3번이 잘 생성됨


이제 우리가 설정한 fallback 옵션이 잘 동작하는지 확인

npm run start


브라우저에서 네트워크 탭의 속도를 조절하고


요청을 보내보면,

처음엔 로딩 페이지 등으로 대체해서 보여주다가,

뒤늦게 데이터를 렌더링함


가장 먼저 받은 html 페이지는 이와 같은데,

왜냐하면 코드 상에서,

book props가 없을 경우 위와 같이 렌더링하도록 했었기 때문임


완성도 높이기

그런데 컴포넌트가 getStaticProps의 계산 결과를 받지 못한 상태를 fallback 상태라고 하는데,
이 fallback 상태일 뿐인데,

위처럼 오류가 발생했다고 하면
사용자는 오류인 줄 알 것임

웹의 완성도를 높이기 위해 이 렌더링되는 텍스트를 로딩이라고 수정하면 좋을 것 같은데,...


이 텍스트를 통째로 바꾸면
로딩이 끝난 이후, 즉 fallback 상태가 끝난 이후
진짜 문제가 발생해도 로딩 중이라고만 출력될 테니,

페이지 컴포넌트가 fallback 상태에 빠져 있을 때만,
로딩 중입니다, 라고 렌더링되도록 수정


그러기 위해서는
useRouter 훅으로 router 객체를 받아와서
이 객체가 fallback인지 확인(isFallback 프로퍼티를 통해서)

fallback이면 데이터를 기다리는 중이니까 로딩 중이라고 렌더링

이후에 fallback이 끝난 후에도 book으로 props(데이터)가 들어오지 않으면
찾을 수 없다는 텍스트를 렌더링


데이터가 있는 페이지는 로딩 후에 데이터가 잘 렌더링되고,


진짜 존재하지 않는 데이터면,
로딩 후 데이터가 없다고 렌더링됨


그리고 또!!
만약에!!!

이렇게 존재하지 않는 페이지로 들어왔을 때
Not Found 페이지로 보내고 싶으면

getStaticProps 함수 안에
book 데이터를 불러왔는데 존재하지 않으면
오류가 있다고 판단해서,

notFound라는 프로퍼티 값을 true로 설정하면
404 페이지로 리다이렉트 함


빌드를 다시 하고 프로덕션 모드로 실행해보면,

로딩 되다가 Not Found 페이지로 이동

☑️ 정리

fallback 옵션

  • false: 존재하지 않은 경로로 요청이 왔을 때 404 Not Found 페이지로 이동
  • blocking: SSR 방식처럼 실시간으로 페이지를 사전 렌더링해서 응답
  • true: blocking 옵션처럼 SSR 방식으로 실시간으로 페이지를 사전 렌더링해서 응답하지만,
    이때 페이지의 생성을 끝까지 기다렸다가 응답하는 게 아니라
    데이터가 없는 fallback 상태의 페이지부터 반환하고
    그 뒤에 데이터는 후속으로 나중에 보내주는 방식
    • 이를 위해 isFallback 이라는 프로퍼티를 이용할 수 있음

📌 ISR 1. 소개 및 실습

ISR (Incremental Static Regeneration)
: 증분 정적 재생성
단순히 SSG 방식으로 생성된 정적 페이지를
일정 시간을 주기로 다시 생성하는 기술

☑️ 기존 SSG 방식과 이 단점

어떤 페이지를 SSG 방식으로 동작하도록 설정해서
해당 페이지를 이렇게 빌드 타임에 미리 정적으로 생성하도록 설정해두면,
이렇게 생성된 페이지는 이 시간 이후 다시 생성되지 않음

따라서 이 페이지를 언제 요청하더라도
똑같은 페이지만 반환


이는 속도는 빠르지만 대신 최신 데이터를 반영하기에는 어렵다는 단점이 존재했음


☑️ ISR 방식

ISR 방식을 사용하면
SSG 방식으로 빌드 타임에 생성된 정적인 페이지를 마치 음식처럼 유통기한 설정

그래서 이 유통기한이 지나기 전까지는 매번 똑같은 페이지를 응답하다가,


유통기한이 끝나면, 넥스트 서버에서 새롭게 해당 페이지를 정적으로 생성해서

이후 발생하는 접속 요청부터는 새로운 페이지를 반환할 수 있도록
일정 시간을 주기로 페이지를 다시 생성하도록 설정 가능


그러나 시간이 타이머처럼 칼같이 업데이트하는 건 아님!!

시간이 왼쪽에서 오른쪽으로 흘러간다고 가정할 때,

SSG 방식대로 빌드 타임에 처음 페이지를 한 번 생성하면서
ISR을 적용해서 60초 이후에 이 페이지를 다시 생성하도록 설정
해두게 되면.,

60초가 지나기 전의 요청에는 V1 버전의 페이지를 반환하다가,
60초 이후에 접속 요청이 발생하면 원래 가진 페이지를 반환
하고,
서버에선 이때 새로운 페이지를 재생성

이때 최신 데이터를 반영한 새로운 버전의 페이지가 새롭게 생성되고,
이후 발생하는 요청에는 새롭게 생성된 V2 버전의 페이지가 반환이 되면서
새로운 데이터가 반영된 페이지가 브라우저의 화면에까지 잘 렌더링될 것


☑️ ISR의 특징

그래서 이렇게 특정 시간 이후 정적으로 생성된 페이지를 새롭게 재생성하는
ISR이라는 방식은 기본적으로 이미 만들어져 있는 페이지를 반환하기 때문에,

굉장히 빠른 속도로 브라우저에게 응답이 가능하다는 기존 SSG 방식의 장점과

주기적으로 페이지를 업데이트해줄 수 있기 때문에 최신 데이터를 반영할 수 있다는 SSR의 장점까지

함께 가지고 있는 렌더링 전략!


☑️ 실습

npm run start

로 프로덕션 시작


어디에 ISR 방식을 적용할까?


이 인덱스 페이지의 추천 도서는 어떨까?

랜덤으로 세 가지의 책을 렌더링해야 하는데,
지난번에 SSG로 작성해버려서
이 부분은 브라우저에서 새로고침을 눌러서 새로 접속 요청을 해도
추천하는 도서가 항상 계속 고정이 되어 있음


그래서 이 인덱스 페이지에다 ISR을 적용해서
이 추천도서를 일정 시간을 주기로 변경해보자

어떻게 하냐면,

이 getStaticProps 의 리턴 객체에
revalidate라는 프로퍼티를 추가!
(revalidate는 재검증하다, 라는 뜻)

값으로는, 몇 초 주기로 이 페이지를 다시 생성할 건지 초 단위로 명시
➡️ 이 페이지의 유통기한!

이 인덱스 페이지를 3초 주기로 재검증하겠다고 설정한 것


다시 빌드해보면,

이제 이 인덱스 페이지에 ISR이 3초 간격으로 적용되었음을 확인

이후 다시 프로덕션 모드로 실행하면,
ISR이 인덱스 페이지에서 잘 적용된 것이 확인 가능


앞으로 Next.js로 페이지를 구현할 때 이 ISR 기법을 이용해 페이지를 구성하는 것 추천



📌 ISR 2. 주문형 재검증(On-Demand ISR)

ISR 방식은 미리 생성해둔 정적 페이지를 응답하기 때문에
매우 빠른 속도로 동작하는 SSG 방식의 장점과

특정 시간을 주기로 페이지를 다시 생성해서
최신 데이터까지 함께 반영할 수 있다는 SSR 방식의 장점까지

함께 갖는 가장 강력한 사전 렌더링 방식


그런데! 이 ISR 방식을 추천하지만

시간 기반의 ISR이 적용하기 어려운 페이지가 있음!


☑️ 기존 ISR의 문제점

이러한 페이지는 시간과 상관없이
수정과 삭제 등 우리의 행동에 따라 페이지의 데이터가 즉각적으로 업데이트 되어야 함!

  • 게시글 수정: 페이지 데이터를 업데이트해서 보이게 해야 함
  • 게시글 삭제: 페이지 데이터를 삭제해서 안 보이게 해야 함

그래서 이걸 기존 ISR 방식대로 적용하려면,

예를 들면 지금처럼 60초마다 설정해뒀다면,

60초가 되기 전에 게시글 수정이 이루어질 수 있음
(수정이야 언제든 될 수 있음)


기존 방식은 60초로 설정한 시간이 되기 전에 업데이트가 일어나도,
60초가 되기 이전에는 수정 이전 버전의 페이지를 렌더링함

그럼 60초가 지나기 전에 접속한 유저들은 게시글이 수정되기 전 이전 버전의 페이지를 보게 될 것
➡️ 최신 데이터를 즉각적으로 반영하기 어려움


추가로 반대 케이스가 있음.

만약에 60초 간격으로 페이지를 다시 생성하도록 설정했는데,
실제 사용자의 게시글은 24시간 이후 발생함

그러면 최신 데이터 반영에는 타이밍에 따라 문제가 없을 수도 있지만,

(24시간 이후 발생한) 수정 전에, 불필요하게 페이지를 생성중

왜냐하면 페이지는 사용자가 수정을 했든 말든 60초마다 재생성되고 있음

실제로는 페이지를 재생성할 필요가 없는데도!
불필요하게!


☑️ 그럼 그냥 SSR로 처리하면 되지 않나?

SSR방식처럼,
브라우저가 요청할 때마다
매번 새롭게 페이지를 사전 렌더링하기 때문에 응답시간이 많이 느려지고,
동시에 접속자까지 많이 몰리면 서버의 부하가 커져버림

(할 일이 많으니까!)

그러니까 정적인 페이지로 처리해주는 게 좋은데,
위에서 말했듯이 ISR 방식으로는 많이 부족함


그래서 기존 아래처럼 시간을 기반으로 페이지를 업데이트 하는 기존의 ISR 방식이 아닌,

요청을 기반으로도 페이지를 업데이트할 수 있는 새로운 ISR 방식도 제공하고 있음


☑️ On-Demand ISR

요청을 받을 때마다 페이지를 다시 생성하는 ISR

그래서 주문이 들어올 때마다 한다고
On-Demand인 것임


그래서 실제 게시글을 수정해서 페이지의 데이터 업데이트가 진짜 필요해졌을 때

이렇게 Next.js 서버에 우리가 직접 페이지를 재생성하라는 요청인
페이지 Revalidate 요청을 보내서
페이지를 이때 다시 생성!

쉽게 말하면, 이 방식은 페이지의 업데이트를 직접 트리거링할 수 있음

이렇게 하면 대부분의 페이지에 최신 데이터를 반영하면서도 페이지를 정적 페이지로써 처리해줄 수 있음!


☑️ 실습

일단 인덱스 페이지를 기본 SSG로 변경


API 라우츠 생성

Revalidate 요청을 처리할 새로운 API 라우츠를 만들자!


먼저 pages의 API 폴더 아래에 새로운 파일 생성
우리가 했던 것처럼 API 라우트 핸들러를 만들어야 함!

우선 리퀘스트와 리스판스를
NextApiRequest와 NextApiResponse 형태로 전달 받음

이제 이 핸들러에서 인덱스 페이지를 요청 받았을 때
revalidate, 즉 재생성 시켜주도록 코드를 작성해보자

그래서 await res 객체의 revalidate라는 메서드를 호출하고

revalidate 함수의 인수로는,
어떤 페이지를 revalidate 시키려고 하는지,
해당 페이지의 경로를 넣어주면 됨

인덱스 페이지의 revalidate(재생성) 시킬 거니까 /로 인수로


그리고 실패할 수도 있으니 try-catch문

요청이 성공했다는 가정 하에
revalidate: true로 설정해서 인덱스 페이지의 재생성이 완료되었다고 응답

revalidate가 실패할 땐 res 객체의 status를 500으로 설정해서
revalidate가 실패했음을 알리고
응답으로 보낼 메시지는 send 메서드로 보냄


이제 이 API에 revalidate라는 주소로 접속 요청을 하면 이 handler가 실행

이 응답 객체의 revalidate 메서드가 호출되어서
인수로 전달한 이 인덱스 경로를 다시 생성하게 됨

그리고 페이지 재생성이 성공하면 revalidate: true란 응답이 돌아올 것

작동 확인

npm run build
npm run start

이제 인덱스 페이지는 아무리 새로고침을 눌러도 변화하지 않음


그런데 만약에 인덱스 페이지를 재생성하게 설정한
API Routes로 요청을 보내면

성공 응답을 받고

콘솔 출력을 확인


그리고 인덱스 페이지로 돌아가서 새로고침을 누르면 데이터가 변함

그러니까 우리가 revalidate 요청을 날려서 인덱스 페이지를 On-Demand ISR 방식으로 생성한 것임

이렇듯 On-Demand ISR이라는 사전 렌더링 방식은
API 라우츠를 통해 요청을 받았을 때 특정 페이지를 다시 생성하도록 만들 수 있어서

사용자의 행동에 따라 데이터가 업데이트 된다거나
특정 조건에 따라 데이터가 업데이트 되어야 하는 페이지를,
정적 페이지로써 유지하고 싶을 때 사용할 수 있다는 점을 알아둘 것


이건 대부분의 케이스를 커버할 수 있는 강력한 사전 렌더링 방식이니까
오늘날 대부분 Next.js로 구축된 웹 서비스에서 활발히 사용되고 있음


📌 SEO 설정하기

기본적인 검색 엔진을 최적화하자!

☑️ 파비콘과 섬네일 파일 이동

public 폴더 안에 새 파비콘과 썸네일 파일 이동

파비콘 교체 완료!

(_document.tsx 에 <link rel="icon" href="/favicon.ico" /> 코드가 있어야 함)

☑️ 페이지별 SEO

불필요한 console.log와 불필요한 import는 삭제하고,
주석도 제거

index 페이지

Next.js에서는 React와 달리 각 페이지별로 메타 태그 설정 가능

대신 Head를 import 해야 함

(next/document는 _document.tsx 파일에만 사용되는 컴포넌트임)


이제 return문 안에 기존 HTML의 메타 태그를 작성하듯 원하는 태그를 작성

이 인덱스 페이지가 카카오톡이나 페이스북 같은 sns에 링크로 공유될 때
필요한 썸네일, 타이틀, 설명 등의 오픈 그래프 태그를 함께 설정해보자

오픈 그래프 태그
웹페이지를 SNS에 공유할 때, 링크 미리보기 모양을 정해주는 메타데이터

og:title: 공유될 때 보이는 제목
og:description: 설명 문구
og:image: 썸네일 이미지
og:url: 대표 URL
og:type: 페이지 유형, 보통 웹사이트면 website

content에는 파일이나, 표시될 내용을 적어줌

이러한 컴포넌트 내부 태그는 셀프 클로징 필수!

기본적인 인덱스 페이지의 SEO 설정 마무리


이제 개발자 도구를 열면

메타태그가 잘 설정 된 것 확인 가능!

search 페이지

서치 페이지에도 설정

적용된 것 확인

book 페이지

북 페이지는 살짝 수정해서,
서버에서 가져온 데이터를 메타 태그로 적용

적용된 것 확인

주의할 점

북 페이지는 동적 경로를 갖는 SSG 페이지
그리고 fallback 옵션으로 true
(미리 paths에 만들어둔 페이지만 미리 빌드 타임에 만들어두고
그 외 경로는 SSR 방식으로 요청이 들어왔을 때 서버에 생성)

이때 브라우저가 페이지의 생성을 너무 오래 기다리지 않게
일단 데이터가 없는 fallback 상태의 페이지 반환


이 함수가 계산하는 페이지 컴포넌트에 필요한 props가 없으면
fallback 상태의 페이지 먼저 반환하는데,

그래서 빌드 타임에 생성되지 않은 경로로 접속 요청을 날리면

설정한 메타 태그가 다 빠져 있음


코드를 보면
페이지 컴포넌트에서,

현재 이 페이지가 fallback 상태면
이 문구를 렌더링하는데


메타 태그는 fallback 상태가 끝나고 나서야,
그러니까 데이터가 다 들어오고 나서야 페이지에 추가됨


지금처럼 이렇게 메타 태그를 fallback 상태가 아닐 때에만
데이터가 불러와졌을 때만 적용이 되게 설정을 해두면,
페이지를 처음 요청했을 땐 메타 태그가 적용이 되지 않아
SEO 설정이 안 되어 버림


그럼 어떻게 해야 함?

이 fallback 상태에도
기본적인 메타 태그를 설정이 되도록 추가해야 함

따라서 해당 조건문 안에 Head 태그를 작성하여,
페이지가 로딩 중인 상황이라도 기본적인 메타태그가 설정되게 함

이제 페이지가 fallback 상태에 있을 때에도
이런 식으로 기본적인 메타태그를 리턴해줌

이와 같이 설정해두는 것이 좋음!



📌 배포하기

vercel에 가입

vercel 설치

Vercel CLI를 내 컴퓨터 전체에서 사용할 수 있도록 설치

vercel 로그인

vercel 배포

지금 지정한 폴더의 프로젝트를 Vercel에 연결하고 배포할까요? 라는 질문

  • yes를 고르면
  • 이 로컬 폴더를 Vercel 프로젝트와 연결하고
  • 설정을 만든 뒤 바로 배포까지 진행합니다.

이 프로젝트를 어느 계정/팀 소속으로 만들지 묻는 것

  • 개인 계정
  • 팀 계정
  • 조직 계정

Vercel에 만들어져 있는 기존 프로젝트와 연결할지 묻는 것

  • yes면 → 기존 Vercel 프로젝트와 이 로컬 폴더를 연결
  • no면 → 새 프로젝트를 만듦

Vercel에 생성될 프로젝트 이름을 정하는 단계

  • Vercel 대시보드에서 보이는 이름
  • 기본 배포 URL 이름
  • 프로젝트 식별용 이름

실제 앱 코드가 어느 폴더에 들어 있나요? 를 묻는 것

  • ./ 는 현재 폴더

자동 감지한 설정을 직접 수정할까요? 라는 질문

  • 추가적인 고급 설정까지 더 바꿀지 묻는 것
  1. 현재 폴더를 배포 대상으로 삼고
  2. 내 계정 소속으로
  3. 새 Vercel 프로젝트를 만들고
  4. 이름은 05-seo
  5. 코드 위치는 현재 폴더
  6. Next.js 설정은 자동 감지값 사용
  7. 추가 설정 수정 없이 바로 배포

주소가 나오면 그 주소로 접속 시

화면은 나오지만 데이터가 없음

왜냐하면 백엔드 서버가 로컬에서 실행되고 있고,
버셀 서버에서는 내 로컬을 모르니까.

백엔드 서버도 같이 배포해야 함

같은 방식으로 배포


그리고 각 lib에 만든 fetch 함수의 url 교체


이제 배포를 다시 해야 함

프로덕션 모드로 재배포


웹에선 빌드되는 상태를 볼 수 있음


잘 배포가 되면 Ready로 나옴


이제 데이터를 잘 배포함!
(사실 vercel에 환경변수까지 설정해줬다.)

KEY는 DATABASE_URL로,
VALUE는 처음 로컬의 .env에 설정한 값으로 해주면 됨


메타 태그, 오픈 그래프 태그도 모두 적용 확인~

0개의 댓글