카테고리 없음

[코드잇] Next.js 렌더링

P1su 2024. 11. 11. 22:25

Image 컴포넌트

Next 의 Image 컴포넌트는 주소를 변경해준다.

해당 컴포넌트를 사용하면 주소를 거쳐서 이미지의 크기를 최적화해준다.

레이지 로딩

꼭 필요한 순간에만 이미지를 다운받아 페이지 로딩 속도를 최적화 할 수 있다.

Image 컴포넌트 사용법

이미지의 크기를 지정하지 않는다면 오류가 발생한다. 지정한 이미지 크기를 기준으로 최적화하기 때문이다.

유연하게 크기를 지정하기 위해서는 fill 속성을 사용한다. 이는 조상 요소에 꽉차로독 채우는 것이다, 따라서 조상요소의 크기를 지정해주면 조금 더 유연하게 크기를 조정할 수 있다.

비율을 유지하고 싶다면 Image 컴포넌트에 objectFit 속성을 적용해주면 된다.

export default function Test() {
  return (
    <>
    <div style={{
      position: 'relative',
      width: '50%',
      height: '200px'
    }}>
      <Image
          src={asset}
          width={400}
          height={400}
          alt="상품 이미지"
          style={{
            objectFit: 'cover',
          }}
        />
    </div>
    </>
  );
}

프로젝트 외부의 이미지를 사용하기 위해서는 next.config.js 에서 경로에 대한 설정을 해주어야 한다. 외부 이미지 서버를 미리 next.js 에 알려주는 역할을 한다고 생각하면 된다.

Head 컴포넌트

웹페이지의 이름을 설정하기 위해서 Head 태그 내부에 title 태그를 작성해주면 된다.

export default function Test() {
  return (
    <>
    <Head>
      <title>테스트</title>
    </Head>

파비콘의 경우 모든 페이지에 공통으로 적용되기 때문에,, _app.js 에서 작성을 해주면 된다. Head 태그 내부에 link 태그로 작성을 해주면 된다.

export default function App({ Component, pageProps }) {
  return (
    <>
      <Head>
        <title>코드잇</title>
        <link rel="icon" href="아이콘 파일 경로" />
      </Head>

빌드와 실행

리액트는 JSX 문법으로 코드를 작성하는데, 웹 브라우저는 이를 바로 해석하지 못하여 트랜스 파일링 과정을 거쳐 JS 문법으로 변경을한 후 실행을 한다.

Next.js 역시 리액트 코드를 브라우저가 실행할 수 있게 변환해주는 빌드를 실행한다. \

npm run build

.next 폴더에 빌드가 완료되어 실행될 파일들이 저장된다.

npm run start / npm start

반드시 빌드가 된 상태에서만 실행이 가능하다.


Next 서버는 웹브라우저로 사이트에 접속하면 주소에 해당하는 페이지에 html 을 보내준다.

그리고 데이터를 받아오면 화면을 업데이트해서 보여준다.

Image 컴포넌트 같은 경우에는 Next 서버 주소로 이미지를 요청하고 서버는 쿼리 문자열이 있는 페이지로 이동해 이미지를 받아오고 이를 최적화한 파일을 보내준다.

npm run dev

개발 모드에 필요한 기능들이 같이 실행된다. 실제 배포된 사이트에서는 이런 기능들이 필요하지 않기 때문에, 빌드를 한 후에 run start 로 프로덕션 모드로 실행을 하는게 바람직하다.


프리렌더링

웹페이지가 로딩되기 전 HTML 문서를 만드는 것 / 렌더링을 하는 것

CSR의 경우 서버에서 HTML, JS 를 차례대로 불러온 후 화면을 렌더링한다.

프리렌더링은 웹브라우저가 HTML 코드를 받아오기 전에 렌더링을 한다.

[정적 생성]

빌드 하기 전 렌더링을 하는 것,,,즉 빌드할 떄 HTML 을 만드는 것.

렌더링된 화면을 바로 보여줄 수 있음, 이후 JS 를 로딩하고 리액트를 시행하면 바로 리액트가 연결이 되어서 이후부터 클라이언트 사이드로 화면을 조작한다.

이미 렌더링된 HTML 과 리액트를 연결하는 작업을 hydration 이라고 한다.

[SSR]

서버사이드 렌더링으로 웹브라우저가 GET 리퀘스트를 보낼 때마다 서버가 렌더링해서 보내준다.


  • 초기 로딩이 빨라진다.
  • 검색 엔진 최적화
    • 이미 렌더링된 HTML 을 받아서 검색 로봇이 사이트를 빠르게 파악할 수 있다.

정적 생성

빌드할 떄 HTML 을 미리 만들어 두는 것.

npm run build 시 .next 폴더에 여러 파일들이 존재하고, index.js 에서 ctrl shift p → Format Document 실행 시 HTML 코드가 정리되어서 보인다. 리액트로 작성한 페이지가 렌더링 되어 있다.

만약 useEffect로 텅 빈 배열을 렌더링하게 된다면 초기 렌더링 된 HTML 파일에는 해당하는 코드의 화면이 보이지 않는다. 화면이 보이고 리액트 코드의 hydration 까지 끝나면 useEffect가 실행되어 데이터를 가져오고, 화면에 렌더링한다. 즉 useEffect 코드는 서버에서 로딩되지 않고 웹 브라우저에서 실행되는 코드이다.

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

Next에서만 사용되는 특이한 문법으로 getStaticProps 함수를 생성후 export 해주면 정적 생성을 할 때 Next 가 이 함수를 실행한다.

prop으로 만들 값을 만드는 함수이다. useEffect 에 해당하는 코드를 위와 같이 작성해주면 초기 렌더링시에도 해당 코드가 렌더링 되는 모습을 확인할 수 있다.

만약 데이터가 자주 바뀌는 상황이면 정적 생성이 적절하다고 볼 수 없다.

위와 같이 코드를 작성하면, index.html 파일에는 script 태그 하나를 볼 수 있는데, props 객체를 지니고 있다. 웹브라우저가 리액트가 로딩할 때 hydration 하면서 해당 객체를 사용하는 용도이다. 해당 pageProps를 활용하여 HTML 과 virtual DOM 상의 컴포넌트를 동기화한다.

다이나믹한 페이지 경로에서 정적 생성

import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';
import instance from './../../lib/axios';

export default function Product() {
  const [product, setProduct] = useState();
  const router = useRouter();
  const { id } = router.query;
  
  const getProduct = async (targetId) => {
    const res = await instance.get(`/products/${targetId}`);
    const nextProduct = res.data;
    setProduct(nextProduct);
  };

  useEffect(() => {
    if (!id) return;
    getProduct(id);
  }, [id]);

  return (
    <div>
      <h1>{product.name}</h1>
      <image
        src={product.imgUrl}
        alt={product.name}
      />
    </div>
  );
};

id 값에 따라 화면이 바뀌는 페이지이다.

먼저 product를 받아오는 코드를 getStaticProps 함수로 작성한다. getStaticProps는 리액트 컴포넌트가 아니기 때문에 hook을 사용할 수 없다는 점을 유의해야한다.

만약 params 값을 가져오고 싶다면 기본적으로 지원해주는 context 객체를 통해서 가져올 수 있다.

추가로 getStaticPaths 를 통하여 생성할 페이지를 정해주어야 한다. paths 함수로 내려준 페이지를 바탕으로 경로를 지정한다.

만약 fallback 값을 true 로 바꾸면, 정적 생성해 놓지 않은 경로로 왔을 때 getStaticProps에서 정적 생성을 하겠다는 것이다.

만약 작성해주지 않은 페이지에 대해 오류가 발생할 경우, try catch 문으로 오류 처리를 해주면 된다.

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: true,
  }
}

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

  return {
    props: {
      product,
    }
  }
}

export default function Product({ product }) {
  const [sizeReviews, setSizeReviews] = useState([]);
  const router = useRouter();
  const { id } = router.query;

  async function getSizeReviews(targetId) {
    const res = await axios.get(`/size_reviews/?product_id=${targetId}`);
    const nextSizeReviews = res.data.results ?? [];
    setSizeReviews(nextSizeReviews);
  }

npm run dev 로 개발모드로 실행하면 매번 페이지에 접속할 때마다 함수를 실행한다. 실제 배포된 페이지에서 정적 생성을 하면 빌드할 때 딱 한번만 실행한다. ㅍ

서버사이드 렌더링

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} />
    </>
  );
}

검색을 통해 쿼리를 설정하고, 이를 렌더링하는 경우 사용자가 설정하는 것이기 때문에 웹 브라우저가 생성된 후 리퀘스트를 보내야만 해당 값을 알 수 있다.

따라서 해당 경우에는 정적 생성을 하지 못하고, SSR을 할 수 있다.

SSR은 브라우저가 리퀘스트를 보낼 때 마다 렌더링을 할 수 있다.

사용법은 정적 생성과 비슷하지만 함수 이름만 다르다.

getServerSideProps 함수를 사용하면 된다.

사용자가 생성하는 자주 바뀌는 부분 역시 SSR로 매번 프리 렌더링을 처리할 수 있다.

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

pageProp을 state 로 만들고 이를 변경하게 되면 화면에 변경사항을 바로 렌더링할 수 있다.

렌더링 과정 살펴보기

Next 는 무조건 정적 생성과 SSR만을 하는 것이 아니고, CSR과 적절하게 섞어서 웹 사이트를 최적화해준다.

처음으로 실행을 하게 되면 빌드를 한 index.html 페이지를 웹 사이트에 그대로 보내준다. 상세 정보 페이지의 경우 HTML 을 새로 받지 않고 필요한 데이터를 JSON 으로 받앙온다. ServerSideProps 를 통해 Props를 만들어서 이를 보내주는 것이다.

더 자세히는 페이지에 해당하는 자바스크립트의 코드도 따오는 것이다. 필요한 부분만 따오는 것을 코드 스플리팅 이라 하고 Next 에서는 기본적으로 모든 페이지를 따로 코드 스플리팅해준다.

페이지를 전부 새로 렌더링 하는 것보다 일부 데이터만 바꾸는 것치 더 효율적이다.

첫 화면 로딩에는 프리 렌더링으로 렌더링된 HTML 파일을 받아오지만 Link 태그를 통해 페이지를 이동할 경우 필요한 자바스크립트 코드랑 JSON 데이터만을 받아와 화면을 렌더링한다.

728x90