React.js NextJS Pre-Rendering

강정우·2023년 2월 2일
0

next.js

목록 보기
2/9
post-thumbnail
post-custom-banner

Pre-Rendering

문제인식

  • 우리가 http request로 어떠한 data를 가져올 때 useEffect 훅을 통하여 가져온다. 이를 통해 loading, error를 핸들링할 수 있기 때문이다.
    그런데 이때 useEffect의 특징을 다시 한 번 명확히 짚고 넘어가야한다. 그것은 컴포넌트 함수가 실행되고 난 후에 useEffect가 실행되는 방식으로 작동한다. 여기서 "이후:라는 것이 중요하다.
function HomePage(){
    const [loadedMeetups, setLoadedMeetups] = useState([]);

    useEffect(() => {
        setLoadedMeetups(DUMMY_MEETUPS);

    },[]);


    return(
            <MeetupList meetups={loadedMeetups}></MeetupList>
    )
}
  • 다시 말하자면, 처음 이 홈페이지 컴포넌트가 렌더링 (이때 state는 비어있는 배열이다) => 그 다음에 이 useEffect 함수가 실행 =>
    state를 업데이트 => 컴포넌트 함수가 다시 실행 => 실제 데이터를 가지고 목록을 다시 렌더링

  • 이렇게 컴포넌트 렌더링이 두 번 실행된다. 첫 번째 렌더링 사이클에서는 위의 컴포넌트가 처음으로 렌더링 되고 loadMeetups 상태는 초기 상태, 즉 이 비어있는 배열이다.

  • 이것은 백엔드에서 위 컴포넌트를 가져올 때 사용자가 잠시 로딩 화면을 보게 된다. 이 두 번의 렌더링 사이클 때문에 검색 엔진 최적화에 문제가 생기기 때문이다.

  • 페이지 소스를 보면 여기에 실제 데이터는 없는 걸 볼수 있다.
    즉, 서버에서 가져온 HTML 페이지에는 HTML 콘텐츠에 들어 있지 않다는 말이다.

  • 하지만 NextJS가 특징으로 추후 자동으로 생성하는 pre-rendering 된 페이지는 이 두 번째 사이클을 기다리지 않는다.
    첫 번째 렌더링 사이클의 결과를 가져와서 pre-rendering한 HTML 코드를 반환한다.
    그러게되면 데이터는 없을 것이다.

문제 정리


1. 사용자가 특정 url로 request를 보낸다.
2. 서버에서는 pre-rendering된 페이지를 로드한다. 이때 useEffect같은 훅은 1회만 실행되므로 이때 사용자에 로드되는 페이지는 데이터가 누락된 페이지가 보여진다.
3. 이때 SEO(Search Engine Optimiztion)엔 좋지만 페이지 데이터 누락
4. 페이지에 hydrate라고 부르는 작업을 수행한다. 즉, React가 페이지를 싱글 페이지 애플리케이션으로 만들고 제어하는 것이다.
이때 data를 받아온다.

이 내장된 사전 렌더링 프로세스를 미세 조정해야 하고 그에 맞는 설정을 해야 하는데 2가지 방법이 있다.

1. Server-side Rendering (Static Generation)

  • Static Generation에서 페이지 컴포넌트가 pre-rendering 되는 시점은 웹앱을 빌드하거나 Next 프로젝트를 빌드하는 시점,
    즉 프로덕션용으로 빌드하는 시점이다.
function HomePage(){
    const [loadedMeetups, setLoadedMeetups] = useState([]);
    useEffect(() => {
        setLoadedMeetups(DUMMY_MEETUPS);

    },[]);
    return(
            <MeetupList meetups={loadedMeetups}></MeetupList>
    )
}

1) getStaticProps

function HomePage(props){
    return(<MeetupList meetups={props}></MeetupList>)
}
export async function getStaticProps() {
  	// fetch data from an API first then
    return {
        props:{
            meetups: DUMMY_MEETUPS,
        }
    };
}
  • pages 컴포넌트 파일에만 해당한다!!

  • reserve된 이름이다. 즉, 예약어이다.

  • NextJS는 해당 함수를 찾고 발견했다면 사전 렌더링 프로세스 중에 이 함수를 실행한다.
    따라서 컴포넌트 함수를 바로 호출하지 않고 반환된 JSX 스냅샷을 HTML 콘텐츠로 사용한다.

  • 비동기도 가능하다. 이 뜻은 NextJS는 이 promise가 해결될 때까지 기다리고 데이터가 읽어 들여진다면 그 다음에 이 컴포넌트 함수에서 사용할 props를 반환한다.

  • 이 함수는 빌드시 실행되는 함수로 파일 시스템에 접근할 수도 있고 데이터베이스에 안전하게 연결할 수도 있지만 서버, 클라이언트 사이드에서는 절대 실행되지 않는다.

  • return 값은 객체로 반환하고 반드시 props property가 있어야만 한다.
    이 props가 바로 해당 컴포넌트 매개변수로 들어갈 prop이 되어 여기서 값을 꺼내온다.

  • 그러면 우리는 더이상 state관리와 useEffect 훅을 사용할 필요도 없어진다.

  • 보다시피 잘 pre-rendering되고 있고 이로인에 SEO과 사용자로 하여금 조금의 로딩속도를 줄였다.

2) getStaticPaths

  • 위의 getStaticProps의 함수의 맹점이 하나 있다.
    getStaticProps는 build 프로세스 때 생성됨으로 동적 경로에는 데이터를 미리 가져올 수 없는 것이다.
  • 그래서 동적 페이지에서 필요한 함수고, 넥스트 JS에게 어떤 동적 매개변수 밸류의 어떤 페이지가 pre-generate 되어야 하는지 알려주는 함수이다.
export async function getStaticPaths() {
    const client = await MongoClient.connect("몽고DBURL")
    const db = client.db();
    const meetupsCollection = db.collection("콜렉션Name");

    const meetupsAry = await meetupsCollection.find({}, { _id: 1 }).toArray();

    client.close();
    return {
        fallback: "blocking",
        paths: meetupsAry.map(meetupAry => ({ params: { meetupId: meetupAry._id.toString() } }))
    }
}
  • 모든 동적 세그먼트 밸류가 있는 객체를 반환한다.
    그리고 반환 객체의 paths 배열에는 객체가 여러 개 있어야 한다. 동적 페이지 버전당 객체가 하나여야 한다.

  • 이 객체는 params 키를 가진다. 이는 반드시 필요한 키값이고 이걸로 동적페이지를 만든다.
    따라서, 동적 페이지 세그먼트가 여러 개 있다면 네스트 객체에 키가 여러 개 있는 것이다.

폴백키 (fallback)

  • 이 키가 넥스트 JS에게 paths 배열이 모든 지원되는 매개변수를 저장할지 아니면 일부만 저장할지 지정한다.
    폴백을 true라고 설정하면 넥스트 JS가 페이지를 만들고 들어오는 요청에 관해서, 서버에서 동적으로 만든다.
    폴백을 false라고 설정하면 404페이지를 내보낸다.

  • 하지만 여기서는 발생하지 않지만 나중에 deploy를 하면 큰 문제가 발생하는데 만약 false라고 했다면 등록하는 어떠한 행동에 대하여서는 404error를 내뿜게 될 것이다.
    왜냐하면 어떠한 data를 insert하면 그 값은 다시 pre-rendering을 안 하기 때문에 무결성을 헤치는 값이 되기 때문이다.

  • 그래서 true, blocking처리를 하면 되는데 이 두 값을 자세히 알아보자

공통점

  • 여기서 지정한 경로 목록이 완전하지 않을 수 있고 더 유효한 페이지가 있을 수 있음을 NextJS에 알려준다.
  • 그래서 NextJS가 바로 페이지를 찾을 수 없는 경우 404페이지로 응답하지않고
    요청 시 페이지를 생성한 후에 캐시에 저장하여 필요할 때 이것을 pre-generate 한다.

차이점

  • true : true로 설정하면 빈 페이지가 즉시 반환되고 동적으로 생성된 콘텐츠를 풀다운한다.
    따라서 페이지에 데이터가 아직 없는 경우를 처리(로딩화면 등)해야 한다.

  • blocking : 페이지가 미리 생성될 때까지 사용자는 아무것도 볼 수 없고 나중에 완성된 페이지가 제공된다.
    blocking은 다른 추가 작업이 필요하지 않기 때문에 true값에 비해 조금 더 편리하다.

2. Server-side Rendering (getServerSideProps)

  • 우선 build 결과를 살펴보자

  • 정적 페이지 5 개를 생성하고 또한 어떤 페이지들을 만들었는지 볼 수 있다.
    이걸 보면 최상단 루트 페이지'?' 를 만들었다는 것도 알 수 있고

  • 동적 페이지(/[meetupId])도 만든 것을 확인 할 수 있다.

  • 404 페이지는 기본값으로 자동으로 만들어진다.

  • 페이지 옆에 아이콘들이 있는데 하나는 채워진 원이고 나머지는 비어있는 원이다.
    밑으로 내려보면 여기 legend를 확인할 수 있는데 채워진 원은 SSG 정적 사이트 생성(Static Site Generation)이다.
    즉, HTML로 자동 생성된 것이다. +JSON은 페이지가 싱글 페이지 애플리케이션으로 전환되면 데이터를 미리 가져오는 데 사용된다.

  • 이 비어있는 원은 1번의 Static generation을 뜻한다. 이때 유일한 차이점은 여기엔 초기 props가 없다는 것 뿐이다.
    따라서 가져온 초기 데이터가 없고 실제로 루트 페이지에서만 데이터를 가져온다. getStaticProps를 추가한 페이지니까

  • 그래서 루트페이지('/')는 채워진 원으로 표시되어 있다.

이때 getStaticProps를 사용하면 문제점

  • 바로 최신정보를 얻을 수 없다는 점이다.
  • 업뎃을 아무리해도 다시 새로고침하면 pre-rendering된 데이터만 가져온다.
    여기 반환된 객체에

1) revalidate prop

export async function getStaticProps() {
  	// fetch data from an API first then
    return {
        props:{
            meetups: DUMMY_MEETUPS,
        },
        revalidate:10
    };
}
  • revalidate 프로퍼티를 추가한다.
    getStaticProps에서 반환된 객체에 이 프로퍼티를 추가하면 incremental static generation(점진적 정적 생성)이라는 기능을 사용할 수 있다.

  • revalidate에는 value로 숫자가 필요하다. 이 숫자는 요청이 들어올 때 이 페이지를 다시 생성할 때까지 NextJS가 대기하는 시간을 초 단위로 표시한 것이다.

  • 즉 revalidate에 어떤 숫자가 설정되어 있으면 페이지는 빌드 프로세스 중에 "바로" 생성되지 않는다.
    이 페이지에 요청이 있다면 서버에서 몇 초 간격으로 생성될 것이다.

  • 여기 숫자는 업데이트 빈도에 따라 결정하면 된다.

  • 다시 말해서 revalidate 값이 10이라면 이 페이지에 요청이 들어오면 적어도 10초마다 서버에서 페이지를 다시 생성한다는 것이다.

2) getServerSideProps

  • 하지만 주기적으로 업데이트하지말고 요청이 들어올 때만 업데이트하고싶다면 어떻게 하면 될까?
export async function getServerSideProps(context){
    // const req = context.req;
    // const res = context.res;
  	// 특정로직 작업 validate or session, cookie
    // fetch data from an API first then
    return{
        props:{
            meetups:DUMMY_MEETUPS
        }
    };
}
  • 이 함수의 특징으로는 getStaticProps과 다르게 build시에 실행되는 것이 아닌 배포한 다음 서버사이드에서 실행된다는 것이다.

  • 하지만 이 방법 또한 잘 작동한다. 페이지가 요청이 들어올 때마다 pre-generated 된다는 것이다.

  • 그렇다면 둘중에 무엇이 더 나은가?
    getServerSideProps이 더 좋아 보일 수 있다. 실제로 나도 그렇게 생각했고
    하지만 그게 단점이 될 수 있다. 요청이 들어올 때까지 페이지가 만들어지기 기다려야 한다는 뜻이니까.
    반대로 getStaticProps은 매번 다시 만드는 대신 캐시하고 다시 사용하는 CDN에 저장되고 서브 되기 때문에 요청이 들어올 때마다 데이터를 다시 만들고 패치하는 것보다 빠르다..

getServerSideProps 콘크리트 요청 객체에 접속해야 할때 context의 res, req 객체가 필요할 때 또는 이 객체로 작업을 할 때, 매초 여러 번 바뀌는 데이터가 있을 때
getStaticProps HTML 파일을 pre-generate 해야할 때 context의 res, req 객체가 필요 없을 때 주기적으로 바뀌는 데이터가 있을 때
그 파일은 CDN에 저장되고 서브 된다. 그리고 요청이 들어올 때마다 데이터를 다시 만들고 패치하는 것보다 빠르다. 따라서 로딩되는 페이지는 getStaticProps일 때 더 빠를 겁니다. 항상 다시 만드는 대신 캐시하고 다시 사용하니까.
profile
智(지)! 德(덕)! 體(체)!
post-custom-banner

0개의 댓글