[NEXT.js] 넥스트 JS를 배워보자 1편 - Pre Rendering

2022. 2. 19. 14:10Library,Framework

728x90

예전부터 꼭 배워보고 싶은 Next JS 프레임워크에 드디어 입문했다.

 

여태까지 관리자 페이지나 랜딩페이지를 만들다 보니 CSR(Client Side Rendering)로도 충분했지만

현재 SEO과 원할하게 이루어져야 하는 프로젝트에 투입되어 SSR을 지원하는 넥스트 JS를 배우고 있다.

 

React 자체적으로도 SSR (서버 사이드 렌더링)이 가능하긴 하지만

생각보다 복잡하기 때문에 이왕 배우는 거 넥스트 JS를 배우고 있다.

 

일단 CRA(Create React App)처럼 create-next-app으로 간단하게 넥스트앱 프로젝트를 생성할 수 있다

 

  npx create-next-app
  # 또는
  yarn create-next-app

 

 


폴더 구조 

 

.next

  • build/배포 시 필요한 파일들이 담겨있는 폴더

 

 components

  • 재활용 가능한 제일 작은 단위의 컴포넌트
  • 필요시 containers, layout 등 더 큰 단위의 컴포넌트들로 세분화 시킴

 

 pages

  • 넥스트앱이랑 리액트 앱의 가장 큰 차이인 것 같다. 넥스트 js는 라우트를 내장하고 있기 때문에 pages에 파일을 생성하게 되면 그 파일명이 라우트가 된다. 이게 아까 언급했던 Automatic Routing인 것 같다. 더 자세한 것 프로젝트를 진행하면서 다뤄보겠다.
  • _app.js
    • 메인 엔트리 포인트 (앱 내에 공통적으로 적용될 레이아웃이 있다면 여기에)
  •  api
    • 해당 폴더에 파일을 추가해서 API 엔드포인트를 설정할수 있음. 해당 폴더에 있는 코드는 클라이언트 코드와 함께 번들되지 않음. 
  •  index.js 브라우저에 랜더링 되어야 할 내용들이 적히는 곳

 

styles

  • 스타일 관련 (css)

 

next.config.js

  • next js 환경 설정 파일 (sass 모듈 등 설정)

 

일단 프로젝트를 새로 만들고 간단하게 JSON placeholder API를 사용해 포스트를 가져오는 작업을 진행해보았다.

 

보통 CSR을 했다면 아래와 같이 불러왔을 거다.

 

//...생략
const [posts, setPosts] = useState([]);
useEffect(() => {
  const fetchData = async () => {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts");
    const posts = await res.json();
    setPosts(posts);
  };
  fetchData();
}, []);
// ...생략

 

위 방법의 문제점 (CSR의 문제점) 은 Pre-rendering을 지원하지 않는다는 것이다.

초기에 빈 html이 있고 JS가 로드된 이후에야 콘텐츠가 보이는 식이다.

하지만 Next JS를 사용하게 되면 Pre-Rendering을 통해 미리 생성된 HTML을 표시하기 때문에 초기 로딩이 빠르고 빌드시 정보가 HTML에 내포돼있기 때문에 검색엔진에 잘 노출이 될 수 있는 것이다.

 

SSR의 최대 장점인 Pre-Rendering은 Next JS를 통해서 생각 외로 간단히 적용 가능하다.

 

Pre-Rendering

공식문서에 따르면 두 가지의 Pre-rendering 방법(Static Generation , Server Side Rendering)을 사용할 수 있는데 두 방법의 차이는 HTML이 만들어지는 시점에 있다.

 

꼭 한 가지 방법을 채택해야 하는 건 아니고 상황에 맞게 한 가지를 선택해 적용하던가 두 가지를 조합해서 사용하던가 하면 된다.

 

Static Generation (추천)

 

빌드 시에 HTML이 만들어지고 요청이 일어나면 처음 만들어진 HTML을 재활용함.

외부 데이터 불러올 시, 아래와 같이 getStaticProps을 통해 pre-rendering을 해줬다.

자세한 내용은 해당 프로젝트의 pages 폴더의 index.js 참고.

 

// ... 생략
export const getStaticProps = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  const posts = await res.json();

  return {
    props: {
      posts,
    },
    revalidate: 20, // 20초후에 페이지를 다시 만들도록 설정할수도 있다 (기본 false)
  };
};
// ... 생략

 

추가적으로 path가 외부 데이터에 의존한다면 getStaticPath을 써주도록 하자.

예를 들어 여러 개에 사진이 있고 그걸 클릭하면 사진에 대한 세부 정보를 볼 수 있게 하려면 id값으로 Dynamic(Automatic) Routing이 되도록 하면 된다.

 

Automatic Routing은 Next Js의 장점 중 하나인데,

페이지에 [param] 형식으로 라우트를 손쉽게 설정할 수 있다.

 

photos/[id]

 

이런 식으로 구조를 잡게 되면 photos/1, photos/32 각각 1, 32 쿼리 객체를 갖게 된다.

특정 id값에 대한 사진 정보를 Pre-render 하고 싶은지는 외부 데이터에 따라 다르기 때문에 앞서 언급한 getStaticPath를 써줘야 한다.

 

아래는 10개의 (id 0-10)의 사진 정보를 getStaticPath를 통해 pre-rendering 하는 과정이다.

이런 식으로 각 사진에 대한 정보를 pre-rendering 하게 되면 각각의 사진에 대한 검색 최적화가 가능하다.

인터넷 쇼핑몰에서 각 제품에 대한 정보들이 잘 노출될 수 있는 이유도 이런 식으로 다 SSR 방식을 쓰기 때문인 것 같다.

 
// ...자세한 내용은 `photos/[id]/index.js` 참고.
export const getStaticPaths = async () => {
  const res = await fetch(
    "https://jsonplaceholder.typicode.com/photos?_start=0&_end=10"
  );
  const photos = await res.json();
  const ids = photos.map((photo) => photo.id);
  const paths = ids.map((id) => {
    return {
      params: { id: id.toString() },
    };
  });
  return {
    paths,
    fallback: false, // 해당 함수로 return 되지 않는 path에 대한 요청은 404페이지로 응답
  };
};
// ...생략

 

지금은 10개의 데이터만 불러왔기 때문에 큰 문제가 없었지만, 만약 데이터가 수천 개가 넘어간다면 빌드 시에 모든 페이지를 생성하는 건 무리일 수 있다.

 

이때 필요한 게 fallback 옵션인데 falsetrueblocking, 이렇게 3가지 값을 줄 수 있다. 

 

false로 설정돼있으면 build 타임에 생성하지 않는 path로 요청이 들어오면 404페이지를 보여준다

 

 true일시에는 몇몇 페이지만 정적으로 생성 후, 요청이 오는 거에 따라 fallback 페이지를 보여준 후 백그라운드에서 정적 페이지를 추가할 수 있다 (빌드, 응답 속도 단축 가능). 

 

blocking은 true와 유사하게 작동하지만 생성되지 않은 path에 대한 요청이 들어올 시 fallback 페이지를 따로 보여주지 않고 그냥 SSR이 작동하는 방식처럼 작동한다.

 


Server-Side Rendering

 

또 하나의 방법은 요청마다 새로운 HTML 생성하는 SSR방식이다.

빌드시 HTML이 만들어지는 Static Generation 보다 당연히 로딩 속도는 느리지만  실시간 업데이트가 자주 일어나는 상황에선 더 적합하다. 아래 함수는 페이지 요청마다 계속 반복적으로 실행되기 때문에 업데이트된 정보를 수시로 props로 전달 가능하다. 

 

// ...생략 index.js참조
export const getServerSideProps = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  const posts = await res.json();

  return {
    props: {
      posts,
    },
  };
};

 

전에 언급했듯이, 공식문서에서 Pre-rendering의 방법으로 Static Generation 방법을 추천하고 있지만 항상 이 방법을 쓰라는 건 아니다.

 

상황에 따라서 그냥 SSR을 해야 할 때도 있고 둘이 나눠서 적절하게 써야 할때도 있다.

 

출처: https://nextjs.org/docs/basic-features/data-fetching/overview

 

Data Fetching: Overview | Next.js

Next.js allows you to fetch data in multiple ways, with pre-rendering, server-side rendering or static-site generation, and incremental static regeneration. Learn how to manage your application data in Next.js.

nextjs.org