반응형
Jake Seo
제이크서 위키 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (715)
    • 일상, 일기 (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 자바스크립트
  • 작업기억공간
  • MSSQL
  • item9
  • 느린 쿼리
  • item8
  • item7
  • 자료구조
  • 싱글톤 패턴
  • 슬로우 쿼리
  • 싱글턴
  • prerendering
  • pnpm
  • 디자인패턴
  • 팩터리 메서드 패턴
  • Javadoc 자바독 자바주석 주석 Comment
  • rust
  • 빈 검증
  • 객체복사
  • 참조 해제
  • 자바 검증
  • 도커공식문서
  • 자바스크립트 면접
  • try-with-resources
  • 프로그래머의 뇌
  • next js app
  • Pre-rendering
  • 스프링 검증
  • 알고리즘
  • 자바스크립트 인터뷰
  • 자바 디자인패턴
  • bean Validation
  • 싱글톤
  • 러스트
  • 메이븐 라이프사이클
  • 이펙티브 자바
  • 추상 팩터리 패턴
  • 서버리스 컴퓨팅
  • Java
  • 외래키 제약조건
  • serverless computing
  • 토비의 스프링
  • 자바
  • Next.js
  • NEXT JS
  • 플라이웨이트패턴
  • 이펙티브 자바 item9
  • 메이븐 페이즈
  • 메이븐 골
  • 이펙티브자바

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 위키 블로그

프레임워크/next.js

Next.js 가 지원하는 Pre-rendering 이란 무엇일까?

2022. 6. 19. 16:53

Next.js Pre-rendering 개요

Pre-rendering 이란, Next.js 가 각 페이지에 대한 HTML 을 생성하는 것을 말한다.

기존의 문제: SPA 의 Client-side 렌더링 문제

원래 React 같은 SPA(Single Page Application) 는 보통 .html 파일 내부에 Root 로 사용될 <div/> 태그 하나만 존재한다.

html 파일에는 아무것도 존재하지 않고, 브라우저를 통해 URL 에 접근하면, 자바스크립트에 의해 Client-side 렌더링이 시작된다. 이렇게 Client-side 자바스크립트 렌더링 방식은 느린 인터넷 환경을 이용하는 사람에게는 잠시동안 Blank page 를 보여주기도 하며, 검색엔진 최적화가 어렵다는 단점이 있다.

문제 해결: Next.js 의 Pre-rendering

Next.js 는 Pre-rendering 을 통해 HTML 페이지를 생성하여 이러한 문제를 해결한다.

Pre-rendering 의 장점은 퍼포먼스가 뛰어나고 SEO(Search Engine Optimization) 에 뛰어나다는 것이다.

만들어진 HTML 페이지는 최소한의 자바스크립트 코드만 연결되어 있다. 페이지가 브라우저에 의해 로드되었을 때, 자바스크립트 코드가 동작하여 페이지를 완전히 인터렉티브하게 만들어준다. 이 과정을 hydration 이라는 용어로 부른다.

만일, 자바스크립트를 비활성화하면 HTML 은 pre-rendering 에 의해 생성되어 초기 화면은 보이지만, 자바스크립트를 이용한 동적인 부분은 작동하지 않는다. hydration 이 작동하지 않았기 때문이다.

Next.js 가 지원하는 두가지의 Pre-rendering 방식

Static Generation 과 Server-side Rendering 이 존재한다. 두 방식의 차이는 '언제 HTML 을 생성하냐' 에 있다.

  • Static Generation (Recommended): 빌드 타임에 HTML 이 생성되어, 매 요청마다 재사용된다.
    • 빌드 타임에 생성된다는 것은 next build 명령어를 실행했을 때 생성되는 것을 의미한다.
    • CDN 에 의해 캐싱도 가능하다.
    • 데이터를 포함하는 HTML 페이지 생성도 가능하다.
  • Server-side Rendering: 매 요청마다 HTML 이 생성된다.

Next.js 는 한가지 방법을 강요하지 않는다. 하이브리드로 매 페이지마다 원하는 방법을 쓸 수도 있다.

성능상의 이유로 Next.js 에서는 Static Generation 을 추천한다. Static Generation 으로 만들어진 페이지는 추가적인 설정 없이도 CDN 에 의해 캐싱이 되어 퍼포먼스 향상을 노릴 수 있다. 하지만 특정 상황에서는 Server-side Rendering 이 꼭 필요할 때도 있긴 하다.

원한다면, Client-side Rendering 을 Static Generation 혹은 Server-side Rendering 과 함께 사용할 수도 있다. 더 자세히 알아보고 싶다면, Data Fetching 문서 를 참고하자.

Next.js 렌더링과 일반 React App 의 소스코드 현실 비교

일반 React App

<!DOCTYPE html>
<html lang="ko">
   <head>
      <script>
         window.trackKakaoPixel = function () {};
      </script>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
      <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=3.0, minimum-scale=1, user-scalable=yes"/>
      <link rel="stylesheet" type='text/css' href="https://cdn.banksalad.com/fonts/noto/NotoSansKR.css">
      <link rel="stylesheet" type='text/css' href="https://cdn.banksalad.com/fonts/jua/style.css">
      <link rel="stylesheet" href="/dist/v2.bundle.css">
      <link rel="stylesheet" href="/dist/v2.vendor.bundle.css">
      <title>금융을 내 편으로 | 뱅크샐러드</title>
      <meta name="description" content="카드, 예적금, 보험, 투자, 대출, 연금, 실물자산까지! 500만이 선택한 내 돈 관리 앱 뱅크샐러드로 새로운 돈 관리를 시작해보세요."/>
      <meta name="keywords" content="뱅크샐러드, 돈관리, 돈관리앱, 카드, 카드추천, 신용카드추천, 체크카드추천, 적금추천, 예금추천, CMA추천, CMA통장추천, 보험비교, 보험비교사이트, 금리계산기, 금리비교사이트, 혜택많은신용카드, 신용카드비교, 체크카드비교, 뱅샐, 샐러드뱅크, 뱅셀, banksalad"/>
      <meta name="og:title" content="금융을 내 편으로 | 뱅크샐러드"/>
      <meta name="og:description" content="카드, 예적금, 보험, 투자, 대출, 연금, 실물자산까지! 500만이 선택한 내 돈 관리 앱 뱅크샐러드로 새로운 돈 관리를 시작해보세요."/>
      <meta property="og:url" content="https://www.banksalad.com/">
      <meta property="og:image" content="https://cdn.banksalad.com/app/meta/introduce-a/og_banksalad.png" />
      <meta name="theme-color" content="#16c89b" />
      <meta name="naver-site-verification" content="6e174e47ce861cc70846d4bf91a36904b89f730e" />
      <meta name="google-site-verification" content="EZ3Ngy-eg4XxWQFF8Y7PwPX2cJYbPi4-h6gcis0S0iA" />
      <meta name="google-signin-client_id" content="602590083136-pg65trppq4if6383rfufkug9q367l1lh.apps.googleusercontent.com">
      <meta name="apple-mobile-web-app-title" content="Banksalad">
      <link rel="apple-touch-icon-precomposed" href="https://cdn.banksalad.com/app/meta/introduce-a/favicon_120x120.png">
      <link rel="apple-touch-icon-precomposed" sizes="196x196" href="https://cdn.banksalad.com/app/meta/introduce-a/favicon_196x196.png">
      <link rel="shortcut icon" type="image/x-icon" href="https://cdn.banksalad.com/app/meta/introduce-a/favicon.ico">
      <link rel="stylesheet" href="https://cdn.banksalad.com/content/style/contents.min.css" />
      <link rel="canonical" href="https://www.banksalad.com/" />
      <!--[if IE 9 ]>
      <script src="https://cdn.banksalad.com/lib/IE9.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/selectivizr/1.0.2/selectivizr-min.js"></script>
      <![endif]-->
      <!-- Page hiding snippet -->
      <style>.async-hide { opacity: 0 !important} </style>
      <script>
         (function(a,s,y,n,c,h,i,d,e){s.className+=' '+y;
             h.end=i=function(){s.className=s.className.replace(RegExp(' ?'+y),'')};
             (a[n]=a[n]||[]).hide=h;setTimeout(function(){i();h.end=null},c);
         })(window,document.documentElement,'async-hide','dataLayer',4000,{'GTM-NVB8B6P':true});
      </script>
      <!-- End Page hiding snippet -->
      <!-- Google Tag Manager -->
      <script>
         (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
                 new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
             j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
             'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
         })(window,document,'script','dataLayer','GTM-NVB8B6P');
      </script>
      <!-- End Google Tag Manager -->
      <script>
         (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
                     (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
                 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
         })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

         ga('create', 'UA-44896653-4', 'auto');
         ga('require', 'GTM-TC3TM64');
         ga('require', 'urlChangeTracker');
         ga('require', 'outboundLinkTracker');
         ga('require', 'cleanUrlTracker', {
             stripQuery: true,
             trailingSlash: 'remove'
         });
         ga('set', 'appName', 'banksalad2:Web');
         ga('send', 'pageview');
      </script>
      <script async src="https://cdnjs.cloudflare.com/ajax/libs/autotrack/2.4.1/autotrack.js"></script>
      <script>
         !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
                 n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
             n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
             t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
                 document,'script','//connect.facebook.net/en_US/fbevents.js');

         fbq('init', '147489888961817');
         fbq('track', "PageView");
      </script>
      <noscript><img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=147489888961817&ev=PageView&noscript=1" /></noscript>
      <script type="text/javascript" charset="UTF-8" src="//t1.daumcdn.net/adfit/static/kp.js"></script>
      <script type="text/javascript">
         var kakaoPixelInit = function(id) {
           try {
             var _kakaoPixel = window.kakaoPixel(id);
             return function(eventName, tag) {
               _kakaoPixel[eventName](tag);
             };
           } catch(e) {
             return function() {};
           }
         }
         window.trackKakaoPixel = kakaoPixelInit('2074218455202443754');
         window.trackKakaoPixel('pageView');
      </script>
      <script>
         window.namespaceEnv = 'production';
         window.apiHost = 'https://api.banksalad.com';
         window.gatewayHost = 'https://api.banksalad.com/v1/lightweightgateway';
      </script>
   </head>
   <body>
      <!-- Google Tag Manager (noscript) -->
      <noscript>
         <iframe
            src="https://www.googletagmanager.com/ns.html?id=GTM-NVB8B6P"
            height="0"
            width="0"
            style="display:none;visibility:hidden"
            ></iframe>
      </noscript>
      <!-- End Google Tag Manager (noscript) -->
      <div id="wrap"></div>
      <div class="g-signin2" style="display: none;"></div>
      <script>
         window.fbAsyncInit = function() {
             FB.init({
                 appId      : '250069005407221',
                 autoLogAppEvents : true,
                 xfbml            : true,
                 version          : 'v9.0'
             });
         };
      </script>
      <script>
         window.kakaoInit = function () {
             Kakao.init('ae341654dec07eb62b8c46edb32646c0');
         };
      </script>
      <script async defer crossorigin="anonymous" src="https://connect.facebook.net/en_US/sdk.js"></script>
      <script async defer onload="kakaoInit()" src="//developers.kakao.com/sdk/js/kakao.min.js"></script>
      <script src="//cdn.banksalad.com/resources/protocol/protocol.min.js"></script>
      <script src="/dist/v2.vendor.js"></script>
      <script src="/dist/v2.bundle.js"></script>
      <script>
         console.log(
             " /$$                           /$$                           /$$                 /$$\n" +
             "| $$                          | $$                          | $$                | $$\n" +
             "| $$$$$$$   /$$$$$$  /$$$$$$$ | $$   /$$  /$$$$$$$  /$$$$$$ | $$  /$$$$$$   /$$$$$$$\n" +
             "| $$__  $$ |____  $$| $$__  $$| $$  /$$/ /$$_____/ |____  $$| $$ |____  $$ /$$__  $$\n" +
             "| $$  \\ $$  /$$$$$$$| $$  \\ $$| $$$$$$/ |  $$$$$$   /$$$$$$$| $$  /$$$$$$$| $$  | $$\n" +
             "| $$  | $$ /$$__  $$| $$  | $$| $$_  $$  \\____  $$ /$$__  $$| $$ /$$__  $$| $$  | $$\n" +
             "| $$$$$$$/|  $$$$$$$| $$  | $$| $$ \\  $$ /$$$$$$$/|  $$$$$$$| $$|  $$$$$$$|  $$$$$$$\n" +
             "|_______/  /\\_______/|__/  |__/|__/  \\__/|_______/  \\_______/|__/ \\_______/ \\_______/"
         )
      </script>
   </body>
</html>

위는 뱅크셀러드의 예인데, 메인 페이지에 <body/> 태그 안쪽 내용에 html 요소가 거의 없는 것을 알 수 있다. <div id="wrap"></div> 와 그 밑에 div 태그 1개정도가 더 있을 뿐이다.

Next.js App

<!DOCTYPE html>
<html>
   <head>
      <style data-next-hide-fouc="true">body{display:none}</style>
      <noscript data-next-hide-fouc="true">
         <style>body{display:block}</style>
      </noscript>
      <meta charSet="utf-8"/>
      <meta name="viewport" content="width=device-width"/>
      <meta name="next-head-count" content="2"/>
      <noscript data-n-css=""></noscript>
      <script defer="" nomodule="" src="/_next/static/chunks/polyfills.js?ts=1655624731846"></script><script src="/_next/static/chunks/webpack.js?ts=1655624731846" defer=""></script><script src="/_next/static/chunks/main.js?ts=1655624731846" defer=""></script><script src="/_next/static/chunks/pages/_app.js?ts=1655624731846" defer=""></script><script src="/_next/static/chunks/pages/index.js?ts=1655624731846" defer=""></script><script src="/_next/static/development/_buildManifest.js?ts=1655624731846" defer=""></script><script src="/_next/static/development/_ssgManifest.js?ts=1655624731846" defer=""></script><script src="/_next/static/development/_middlewareManifest.js?ts=1655624731846" defer=""></script>
      <noscript id="__next_css__DO_NOT_USE__"></noscript>
   </head>
   <body>
      <div id="__next" data-reactroot="">
         <nav class="jsx-faaba1baeedd438e"><a class="jsx-faaba1baeedd438e active" href="/">Home</a><a class="jsx-faaba1baeedd438e " href="/about">About</a></nav>
         <div>
            <h2>Index Page</h2>
         </div>
      </div>
      <script src="/_next/static/chunks/react-refresh.js?ts=1655624731846"></script><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"development","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script>
   </body>
</html>

body 태그 내부에 div 태그 등 다양한 내용이 pre-rendered 되어 보이는 상태이다.

레퍼런스

  • https://nextjs.org/docs/basic-features/pages
반응형
저작자표시 비영리 (새창열림)

'프레임워크 > next.js' 카테고리의 다른 글

Next.js 에서 Hydration 이란 무엇인가?  (0) 2022.06.23
Next.js 의 빌트인 태그인 next/head 는 무엇일까?  (0) 2022.06.20
Next JS 에서 스타일링을 적용하는 다양한 방법  (0) 2022.06.18
Next.js APP _app.js 의 모든 것  (0) 2022.06.18
next.js 의 라우팅은 일반적인 페이지의 라우팅과 무엇이 다른가?  (0) 2022.06.14
    '프레임워크/next.js' 카테고리의 다른 글
    • Next.js 에서 Hydration 이란 무엇인가?
    • Next.js 의 빌트인 태그인 next/head 는 무엇일까?
    • Next JS 에서 스타일링을 적용하는 다양한 방법
    • Next.js APP _app.js 의 모든 것
    Jake Seo
    Jake Seo
    ✔ 잘 보셨다면 광고 한번 클릭해주시면 큰 힘이 됩니다. ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바