Next.js로 웹사이트 만들기 (4) 프리렌더링

LeeKyungwon·2024년 6월 3일
0

프론트엔드 

목록 보기
43/56
post-custom-banner

프리 렌더링

웹페이지 로딩 이전에 하는 렌더링
Next.js에서는 기본적으로 모든 페이지를 정적 생성한다.

프리렌더링이 일어나는 시점은 브라우저가 HTML 받아오기 전이다.

  • 정적 생성
    빌드를 하는 시점에 렌더링 하는 것 (빌드할 때 HTML을 만들어 놓는다.)
    렌더링 된 화면을 바로 보여줄 수 있음

Hydration

이미 렌더링 된 HTML과 리액트의 데이터를 연결하는 작업

  • 서버사이드렌더링
    웹 브라우저가 GET 리퀘스트를 보낼 때마다 서버가 매번 렌더링해서 보내줌

프리 렌더링의 장점

  • 초기 로딩이 빨라진다.
  • 검색 엔진 최적화가 된다.(텅 빈 HTML이 아니라 렌더링 된 HTML을 받음)

정적 생성

프로젝트를 빌드하는 시점에 미리 HTML을 렌더링하는 것
Next.js에서는 이미 정적 생성을 제공하고 있다.

npm run build

.next/server/pages/index.html에서
Cmd+Shift+P,
Format document
-> HTML 코드가 정리 되어서 보인다. (React에서 작성한 코드가 HTML로 정리되어 보임)

개발 모드가 아니라 프로덕션 모드로 실행

npm run start

HTML을 보내준다.

useEffect에서 실행하는 코드는 프리렌더링 할 때 서버에서 실행되지 않고 웹 브라우저가 다 로딩되고 나서 웹 브라우저에서 실행되는 코드이다.

정적 생성할 때 데이터도 함께 가져오는 법 (getStaticProps())

getStaticProps 이름이랑 export는 정해진 약속이기 때문에 반드시 지켜줘야 한다.

export async function getStaticProps() {
    const res = await axios.get('/products');
    const products = res.data.results;

  return{
    props:{
      products,
    };
}
    
export default function Home({products}) {
  return (
    <>
      <Head>
        <title>Codeitmall</title>
      </Head>
      <SearchForm />
      <ProductList className={styles.productList} products={products} />
    </>
  )
}

(이 함수에서 리액트 훅은 사용 불가)
위 코드는 정적 생성을 할 때 next.js가 실행할 함수를 구현한 것이다.

이름을 getStaticProps로 짓고 export를 하면 정적 생성을 할 때 next.js가 이 함수를 실행해준다.
정적 생성을 할 때 props으로 내려줄 값을 만드는 함수,
페이지 컴포넌트에 props로 내려준다.

저장하고 다시 빌드하면 상품 목록까지 렌더링 된 것을 볼 수 있다.

자주 바뀌는 데이터 같은 경우에는 정적 생성이 적절하지 않다.

정리
Next.js에서는 기본적으로 정적 생성을 해준다.
만약 정적 생성을 하면서 데이터를 가져와서 쓰고 싶으면 getStaticProps라는 함수를 만들고 export 한 다음에 리턴 값으로 props를 만들어주고 페이지 컴포넌트에서 가져와서 사용하면 된다.

정적 생성 : 모양이 고정된 페이지 X -> 빌드할 때 프리렌더링을 하는 페이지

다이나믹한 페이지 경로에서 정적 생성 하는 법 (getStaticPaths())

getStaticProps 함수 안에서는 useRouter를 사용할 수 없다.
대신에 파라미터로 받는 context 객체에서 params 값을 가져올 수 있다.
그리고 getStaticPaths 함수를 사용해서 어떤 페이지들을 미리 만들어야 할 지 알려줘야 한다.

export async function getStaticPaths(){
  return{
    paths: [
      {params:{id:'1'}},
      {params:{id:'2'}},
      ],
    fallback: false,
  }
}
export async function getStaticProps(context) {
  const productId = context.params['id'];
  const res = await axios.get(`/products/${productId}`);
  const product = res.data;

  return{
    props:{ product };

}

export default function Product({product}) {
  ...

다이나믹한 페이지를 정적 생성할 때 어떤어떤 페이지를 생성해야 하는지 정해주는 용도

params 값은 사이트 주소에서 가져오는 값이므로 문자열 형태로 작성해야 함

fallback

없는 경로에 대한 처리
false로 처리하면 404페이지로 이동한다.
fallback: true 라고하면 생성되지 않은 페이지로 접속했을 때 getStaticProps() 함수를 실행해 페이지를 만들어서 보여준다.
fallback: true라고 지정했다면, 필요한 데이터가 존재하지 않을 수 있기 때문에 적절한 예외처리를 해야 하는데, { notFound: true } 를 리턴하면 데이터를 찾을 수 없을 때 404 페이지로 이동시킬 수 있다.

if (!product) return (
  <div className = {styles.loading}>
    <Spinner/>
  </div>
};

이런 식으로 로딩 화면이 보이다가 서버에서 getStaticProps를 실행해서 Props를 받아오면 화면이 보인다.( 이 코드는 페이지 컴포넌트 안에 위치)

이상한 경로로 접근하는 경우 try catch문을 활용해 오류를 잡고 404페이지로 가게 한다.

export async function getStaticProps() {
  const productId = context.params['id'];
    try {
      const res = await axios.get(`/products/${productId}`);
      const product = res.data;
  } catch {
    return{
      notFound: true;
  return{
    props:{
      product
    };

}

모든 상품을 api에서 가져온 다음에 params id 값으로 매핑할 경우 (일일이 안 가져오고)

export async function getStaticPaths(){
  
  const res = await axios.get(`/products/`);
  const products = res.data.results;
  const paths = products.map((product) => ({
    params: {id: String(product.id)},
  }));
  
  return{
    paths,
    fallback: false,
  };
}

영화 사이트 페이지에서의 사용 예시

import MovieList from '@/components/MovieList';
import SearchForm from '@/components/SearchForm';
import styles from '@/styles/Home.module.css';
import axios from '@/lib/axios';

export async function getStaticProps() {
  const res = await axios.get('/movies/');
  const movies = res.data.results ?? [];
  return {
    props: { movies },
  };
}

export default function Home({ movies }) {
  return (
    <>
      <SearchForm />
      <MovieList className={styles.movieList} movies={movies} />
    </>
  );
}

서버사이드 렌더링

프리렌더링을 하고 싶은데 query string 값에 따라서 다르게 렌더링 하고 싶을 때는 서버사이드 렌더링을 사용한다.

getServerSideProps 함수 사용

search.js

export async function getServerSideProps(context) {
  const q = context.query['q'];
  
  const res = await axios.get(`/products/?q=${q}`);
  const products = res.data.results ?? [];
  
  return {
    props: {
      products,
      q,
    },
  }
}

export default function Search({q, products}){
  return (
    <>
      <Head>
        <title>{q} 검색 결과 - Codeitmall</title>
      </Head>
      <SearchForm initialValue={q} />
      <h2 className={styles.title}>
        <span className={styles.keyword}>{q}</span> 검색 결과
      </h2>
      <ProductList className={styles.productList} products={products} />
    </>
  );
}

[id].js
사이즈 추천 부분은 많이 바뀌는 정보이다. -> 서버사이드 렌더링 한다면 해결 가능
주의! Next.js는 정적 생성과 서버사이드 렌더링을 동시에 할 수는 없다.

export async function getServerSideProps(context) {
  const productId = context.params['id'];
  let product;
  try {
    const res = await axios.get(`/products/${productId}`);
    product = res.data;
  } catch {
    return {
      notFound: true,
    };
  }

  const res = await axios.get(`/size_reviews/?product_id=${productId}`);
  const sizeReviews = res.data.results ?? [];


  return {
    props: {
      product,
      sizeReviews,
    }
  }
}

export default function Product({ product, sizeReviews }) {
  ...

클라이언트에서 데이터 주고 받기

필요한 데이터를 컴포넌트의 props로 받고 state에 기본 값으로 담아놓은 다음에 state를 사용해서 렌더링을 한다.
사용자가 입력폼을 작성하고 나서 데이터를 전송하면 서버에서 데이터가 변경되고 그 결과를 보내준다.
클라이언트는 그 결과를 state에 곧바로 반영해서 state 값이 바뀌면 페이지 컴포넌트가 다시 렌더링.
즉, 화면에 데이터가 곧바로 렌더링

이런식으로 페이지 컴포넌트를 state로 만들고 이걸 변경하는 식으로 활용하면 프리렌더링을 하면서도 인터렉티브한 페이지를 만들 수 있다.

export default function Product({ product, sizeReviews: initialSizeReviews }) {
  const [sizeReviews, setSizeReviews] = useState(initialSizeReviews);
  
  async function handleSubmit(e) {
    e.preventDefault();
    const sizeReview = {
      ...formValue,
      productId: product.id,
    };
    const res = await axios.post('/size_reviews/',sizeReview);
    const newSizeReview = res.data;
    setSizeReviews((prevSizeReviews)=>[
      newSizeReview,
      ...prevSizeReviews,
      ]);
  };
  
  async function handleInputChange(e) {
    const {name, vaule} = e.target;
    handleChange(name, value);
  }
  async function handleChange(name,value){
    setFormValue({
      ...formValue,
      [name]: value,
    });
  };
  
  if (!product) return (
    <div className={styles.loading}>
      <Spinner />
    </div>
  );

  return (

렌더링 과정

Next.js에서는 기본적으로 모든 페이지를 코드 스플리팅해준다.
맨처음 접속했을때는 프리렌더링 된 HTML을 전부 보내주고
적절한 타이밍에 코드 스플리팅 된 자바스크립트 코드를 받아서 리액트에서 실행하고
Link를 클릭하면 필요한 데이터를 JSON으로 받아와서 화면을 업데이트한다.

App router

App Router의 가장 큰 차이는 /pages 폴더가 아닌 /app 폴더에 페이지 컴포넌트들을 추가한다는 것이다.
그리고 이 페이지 컴포넌트들은 기본적으로 리액트 서버 컴포넌트(React Server Component)이다.(서버에서만 렌더링되는 컴포넌트)

React Server Component(RSC)란?

리액트 서버 컴포넌트는 서버에서만 렌더링하는 컴포넌트

리액트 서버 컴포넌트와 리액트 클라이언트 컴포넌트의 문법에서 가장 큰 차이는 데이터를 가져오는 방식이다.
Next.js에서 프리렌더링을 한다면 데이터를 Props로 내려받았는데, 리액트 서버 컴포넌트를 사용하면 컴포넌트를 async/await 함수로 만들 수 있고, 함수 최상위(top-level)에서 await 로 데이터를 가져올 수 있다.

async function getData() {
  const res = await fetch('https://api.example.com/...');
  return res.json();
}
 
export default async function Page() {
  const data = await getData();
 
  return <main> ... </main>;
}

참고 자료
React Labs: What We've Been Working On – March 2023 – React
Server Components - Next.js
Introducing Zero-Bundle-Size React Server Components

post-custom-banner

0개의 댓글