2023.05.31 TIL

0

TIL

목록 보기
30/37
post-thumbnail

오늘의 나는 무엇을 잘했을까?

오늘을 Next.js의 핵심 내용과 사용 의의에 대해서 공부했는데, 드디어 SSR과 SG가 와닿기 시작한 것 같다. 처음에는 각 방식이 어떻게 동작하는지 보고 읽고 들어도, 이해는 가는데 왠지 피부로 와닿는 느낌은 없었는데, 강의를 들으면서 실제 페이지를 로드할 때 뭐가 어떻게 달라지는지 보니 이제는 이해가 완벽히 된 것 같다.

오늘의 나는 무엇을 배웠을까?

Next.js

  • Head 컴포넌트

    • html에서 head 태그에 해당하는 Next.js의 빌트인 컴포넌트이다.

    • pages 폴더에 있는 페이지 컴포넌트에 Head 컴포넌트를 쓰고 안에 title과 같은 태그들을 작성하면 된다.

       const HomePage = ()=>{
       ...
       	return (
       	  <>
       			<Head>
       				<title>페이지 제목</title>
       			</Head>
       			...
       		</>
       	);
       }
    • Head 태그 컴포넌트 안에 favicon또한 다음과 같이 넣을 수 있다. 보통 _app.js에 넣음으로써 모든 페이지에 공통으로 들어가게 해둔다.

       const App = ({Components, pageProps})=>{
       ...
       	return (
       	  <>
       			<Head>
       				<title>페이지 제목</title>
       				<link rel="icon" href="/favicon.ico" />
       			</Head>
       			...
       		</>
       	);
       }
    • next에서 구글폰트 적용하기

      • 다음과 같이 @next/font/google 이라는 패키지에서 원하는 폰트를 임포트하고 객체를 만든다.
        import { Noto_Sans_KR } from '@next/font/google';
        
        const notoSansKR = Noto_Sans_KR({
          weight: ['400', '700'], // 문자열 주의
          subsets: [], // 영문(latin), 한글등 서브셋 지정, 빈 배열이면 전부 사용 
        });
      • 폰트를 적용하려면 다음과 같이 notoSansKR객체의 className 프로퍼티를 사용할 수 있다.
        <main className={notoSansKR.className}>
          ...
        </main>
      • 또는 아래와 같이 Head 컴포넌트로 전역적으로 적용할 수 있다.
        <Head>
          <style>{`
            html {
              font-family: ${notoSansKR.style.fontFamily}, sans-serif;
            }
          `}</style>
        </Head>
      • 구글 사이트에서 폰트를 임포트 해오지 않고, 우리 서버에서 가져오게 되므로 폰트 import시 최적화가 지원된다.
    • 빌드와 실행하기

      • 리액트와 마찬가지로 next.js에서도 jsx문법을 자바스크립트로 변환하는 트랜스파일링 과정이 필요하다. (빌드)

      • 그 밖에도 서버 실행에 필요한 코드들을 생성해야 한다.

      • npm run build

        • .next라는 폴더가 생성되고, 그 안에 실행에 필요한 파일들이 들어가게 된다.
      • npm run start

        • 빌드 이후 서버 실행하는 명령. .next 폴더가 있어야 명령이 실행된다.
        • npm run dev는 개발모드, npm run start는 프로덕션 모드
        • localhost:3000에서 next.js 서버가 실행된다
      • next.js 서버의 역할

        • 아래와 같이 클라이언트가 페이지를 요청하면 해당 페이지의 html을 만들어 보내준다. (서버 사이드 렌더링)
        • 또한 다음과 같이 Image태그에 있는 url에 대한 리퀘스트를 외부 서버로부터 받아와 최적화된 이미지를 클라이언트에게 보내주기도 한다.
        • 다음과 같이 어떤 상황에서는 클라이언트의 API 서버로의 요청을 중개하여 api 리소스가 반영된 html파일을 만들어 주기도 한다.
  • vercel로 배포하기

    • vercel 홈페이지로 이동하여 가입 진행(Github로 가입하면 Github연동 배포 가능)
    • 가입을 진행한 뒤 이런 GitHub에 Vercel 앱을 설치하겠다는 팝업이 뜨면 아래의 Install 버튼을 클릭
    • 그럼 내 리포지토리 목록이 Vercel에서 보인다. 아까 만든 Next.js 프로젝트 리포지토리를 Import 한다.
    • Import시 여러 설정을 할 수 있다. 우선 Deploy를 눌러서 배포를 진행한다.
    • 조금 기다리면 배포가 완료되고, DOMAINS에 적혀 있는 주소로 접속하면 배포된 앱을 확인할 수 있다.

Next.js의 프리렌더링

  • 프리렌더링: 미리 렌더링 하는 것

    • 아래와 같이 html 문서를 서버에서 클라이언트로 보내기 이전에 렌더링이 일어나는 것을 프리렌더링이라고 한다.

      프리렌더링은 Static Generation과 서버사이드 렌더링 방식으로 나뉜다.

    • Static Generation: 빌드 타임에 html문서를 렌더링하는 것

      • 미리 html문서가 있으므로 브라우저가 사이트에 접속하면 별도의 작업 없이 그대로 보내준다.
      • 이미 html이 렌더링 된 상태에서 js를 로딩한 뒤 리액트를 실행하게 된다.
      • 이렇게 정적인 html에 js로딩이 끝나고 리액트를 실행할 수 있게 만드는 과정을 hydration이라고 한다.
    • 서버사이드 렌더링

      • 브라우저가 GET리퀘스트를 보낼 때마다 서버가 렌더링을 진행하여 보내주는 방식
    • Static Generation 실습

      • 어떤 페이지에서 useEffect를 실행하여 서버에 api요청을 해서 데이터를 화면에 뿌려준다고 하자. 이런 경우 useEffect는 리액트 코드이기 때문에 hydration이 완료되고 나서야 실행된다. 정적 생성시에는 실행되지 않는다는 의미이다.
      • Next.js에서는 이런 데이터들도 프리렌더링할 수 있게 해준다. 다음과 같은 getStaticProps라는 빌트인 함수를 통해서 가능하다.
        export async getStaticProps (){
          const res = await axios.get('/products');
          const products = res.data.results;
        
          return {
        		props: {
              products,
            }
          }
        }
        
        export default function Home({products}){
          // ...prop으로 정적생성한 products를 받아 사용 (프리렌더링 된 데이터 사용)
        }
      • 위의 예시에서는 products가 값이 자주 바뀌지 않는다고 판단하여 해당 데이터를 정적 생성하고 있다. 값이 자주 바뀌는 동적인 데이터들은 정적생성에 적합하지 않다.
      • 정적 생성은 페이지 모양이 정적이라는 의미가 아니라, 빌드할 때 프리렌더링 된다는 의미이니 헷갈리지 말자.
      • 각 product마다 존재하는 다이나믹 라우팅 된 페이지들을 정적 생성할 수도 있다. 다음과 같이 getStaticPaths라는 빌트인 함수를 통해 어떤 페이지들을 정적 생성할 것인지 미리 정해준 뒤(경로를 정적 생성), getStaticProps함수에서는 context라는 파라미터를 이용해서 경로(product의 id) 변수를 사용하게 하면 된다.
        export async getStaticPaths (){ // 경로를 정적 생성
          return {
        	  paths: [
              { params: { id: '1' } }, // 사이트 주소에서 가져오는 값(상품의 id), 문자열 주의
        			{ params: { id: '2' } },
            ],
        		falllback: false, // 없는 경로에 대한 처리 지정 프로퍼티
        	}
        }
        
        export async getStaticProps(){ // 페이지를 정적 생성
        	const productId = context.params['id'];
        	const res = await axios.get(`/products/${productId}`);
          const product = res.data;
        	
        	return {
        		props: {
        	    product,
        		}
        	}
        }

        주의점: 만약 파일명이 pages/users/[userId].js 였다면, params 객체도 userId 키값을 가지고 있어야한다.

      • fallback 활용: fallback은 static paths에 없는 경로에 대한 처리를 지정한다. 위의 코드에서는 1번, 2번 상품에 대해서만 정적 생성을 하고 있다. fallback이 false인 상태로 3번 경로로 가면 404페이지가 보인다.
      • fallback을 true로 해놓으면 3번 상품부터는 getStaticProps의 동작이 바뀌게 된다. 다음은 fallback이 true일 경우 getStaticProps가 동작하는 과정이다.
        • getStaticPaths가 반환한 path들은 빌드 타임에 HTML로 렌더링된다
        • 이외의 path들에 대한 요청이 들어온 경우, 404 페이지를 반환하지 않고, 페이지의 "fallback" 버전(로딩 스피너나 스켈레톤)을 먼저 보여준다
        • 백그라운드에서 Next js가 요청된 path에 대해서 getStaticProps 함수를 이용하여 HTML 파일과 JSON 파일을 만들어낸다
        • 백그라운드 작업이 끝나면, 요청된 path에 해당하는 JSON 파일을 받아서 새롭게 페이지를 렌더링한다. 사용자 입장에서는 [ fallback 페이지→ 풀 페이지 ]와 같은 순서로 화면이 변하게된다.
        • 새롭게 생성된 페이지를 기존의 빌드시 프리렌더링 된 페이지 리스트에 추가한다. 같은 path로 온 이후 요청들에 대해서는 이때 정적 생성한 페이지를 반환하게된다. "fallback" 상태일 때 보여줄 화면은 next/router의 router.isFallback 값 체크를 통해서 조건 분기하면 된다. 이때 페이지 컴포넌트는 props로 빈값을 받게된다. 백그라운드에서 정적 생성이 끝나면 진짜 props값을 받게 되고, 이후로 사용자는 풀 페이지를 보게 된다.
          function Post({ post }) {
            const router = useRouter();
           
            // If the page is not yet generated, this will be displayed
            // initially until getStaticProps() finishes running
            if (router.isFallback) {
              return <div>Loading...</div>;
            }
           
            // Render post...
          }
      • try-catch로 잘못된 요청 처리하기
        • 아래와 같이 try catch문으로 getStaticProps의 요청 부분을 감싼 뒤 문제가 생기면 notFound: true를 리턴한다. 이제 없는 경로의 요청은 404로 가게 된다.
          export async function getStaticProps(context){
            const productId = context.param['id'];
          	let product;
          	try{
          		const res = axios.get(`/products/${productId}`);
          		product = res.data;
          	}catch{
          		return { notFound: true, };
          	}
          	return {
          		props: {
          		  product,
          		}	
          	}
          }
      • 모든 경로 정적 생성해보기
        • 아래와 같이 모든 products에 대한 번호를 가져와서 전부 paths에 넣어준다.
          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,
          	}
          }
    • SSR

      • 검색 쿼리가 경로로 들어오는 경우, 사용자가 어떤 쿼리를 보낼지 몰라 정적 생성을 할 수 없다. 이런 경우 SSR을 하게 된다.
      • SSR은 getServerSideProps라는 함수로 구현할 수 있다.
        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,
        		}
        	}
        }
        
      • 상품 상세 페이지 등도 SSR을 할 수 있다. 예를 들어, 상품 리뷰와 같이 계속해서 추가되는 동적인 데이터들은 SSR로 가져오면 좋다.
      • 참고로 한 페이지에 SG와 SSR을 동시에 할 수는 없다. getStaticPaths와 getServerSideProps함수가 함께 있을 수 없다.
    • 클라이언트에서 데이터 주고받는 패턴

      • 일반적인 경우 next.js에서 페이지의 초기 내용은 SG나 SSR로 가져오게 되는데, 해당 렌더링의 결과는 페이지 컴포넌트의 props로 받게 된다. 그리고 페이지 컴포넌트 안에서 각각의 props에 대응하는 state를 만들어서 화면에 사용자의 인터랙션을 구현한다. 상품목록을 SSR하여 products라는 prop으로 받아왔다면, newProducts라는 이름의 state를 만들어 초기에 받아온 products를 newProducts state의 초깃값으로 사용하고, 사용자가 새로운 product를 업로드하면 post요청과 함께 setNewProducts를 하여 화면을 업데이트 하는 패턴을 많이 사용한다.

Next.js의 렌더링 과정

Next.js에서 정적 생성 페이지, SSR로 페이지를 만들었다 해서 CSR을 전혀 하지 않는 것은 아니다. Next.js는 SG, SSR, CSR을 적재 적소에 실행하는 최적화가 내부 구현되어 있다.

예를 들어, SSR로 상품 상세 페이지를 만들었다고 할 때, 홈페이지의 특정 링크를 통해 해당 상세 페이지로 이동하는 링크(버튼)이 있다면, 그 페이지의 getServerSideProps함수를 통해 새로운 html이 아닌 필요한 데이터를 담은 json만 새로 생성한다. 또한 해당 페이지 컴포넌트를 js조각으로 미리 로드해 놓는다. 사실은 그 페이지로 이동할때 SSR을 했다 해서 html을 새로 받는 것이 아니었던 것이다. 로드한 js파일을 살펴보면 우리가 컴포넌트로 만든 page를 transpile한 js코드가 들어있음을 알 수 있다.

이렇게 필요한 js파일을 그때 그때 불러오는 기법을 code splitting이라고 한다. Next.js의 페이지들은 모두 자동적으로 code splitting 되어있다.

예외는 url을 통해 어떤 페이지를 get요청했을 경우이다. 이런 경우에는 json만으로 페이지를 가져올 수 없고, SSR을 통해 html을 새로 생성해야 한다.

SSR로 만들었다 해서 해당 페이지로 이동하면 항상 MPA처럼 화면이 깜빡이고 새로 전체를 리로드하는 것이 아니라, next.js에서 제공하는 링크 컴포넌트를 통해 페이지를 이동하면 기존의 SPA처럼 부드러운 화면전환이 일어나도록 잘 설계되어 있는 것이다.

그리고 이런 최적화가 Next.js를 사용하는 가장 큰 의의이자 핵심적인 아이디어 이다.

SSR을 고려하면 좋은 경우

1. 항상 최신 데이터를 보여줘야 하는 페이지
2. 데이터가 자주 바뀌는 페이지
3. 리퀘스트의 데이터를 사용해야 하는 페이지(예: 헤더, 쿼리스트링, 쿠키 등)

의외에 특별한 이유가 없다면 Next.js팀은 무조건 정적 생성을 우선적으로 하는 것을 권장한다고 한다.

오늘의 나는 어떤 어려움이 있었을까?

면접 스터디를 하면서 이전에 학습했던 개념들이 머릿속에서 휘발되고 있다는 것을 알았다. 매일 부담없이 진행하고 있는 스터디인데, 오늘은 꽤 중요하다고 널리 알려진 개념인 쿠키와 웹 스토리지에 대한 질문을 받았는데 대답을 명확하게 못했다. 내가 그 내용을 학습할 때 온전히 내 것으로 만들지 못하고 넘어갔다는 것을 알게 되었다. 어떻게 보면 면접 스터디를 통해 약점을 알게 되고 보완할 수 있게 되어서 좋은 일이라고도 할 수 있다.

내일의 나는 무엇을 해야할까?

  • 알고리즘 문제 풀기
  • 발표 자료 준비하기 (CSR, SSR)
  • 위클리미션 시작하기
  • 데일리미션 풀기

0개의 댓글