본문 바로가기
📖 책 찢기/모던 리액트 Deep Dive

[모던 리액트 Deep Dive] 4장. 서버 사이드 렌더링

by 짱돌보리 2024. 7. 10.
728x90

https://github.com/Study-FE-Techbook/Modern-React-Deep-Dive

 

GitHub - Study-FE-Techbook/Modern-React-Deep-Dive: 모던 리액트 딥다이브 스터디

모던 리액트 딥다이브 스터디. Contribute to Study-FE-Techbook/Modern-React-Deep-Dive development by creating an account on GitHub.

github.com

 

[4장] 서버 사이드 렌더링

4.1 서버 사이트 렌더링이란?

✨싱글 페이지 애플리케이션의 세상

📍싱글페이지 애플리케이션이란

  • 렌더링과 라우팅에 필요한 대부분의 기능을 브라우저의 자바스크립트에 의존하는 방식
  • 최초에 첫 페이지에서 데이터를 모두 불러온 이후에는 페이지 전환을 위한 모든 작업이 자바스크립트와 브라우저의 history.pushState와 history.replaceState로 이뤄지기 때문에 페이지를 불러온 이후에 하나의 페이지에서 모든 작업을 처리한다.

history.pushState
브라우저의 히스토리에 새로운 상태를 추가 (뒤로가기 O) 사용자는 새 URL로 이동한 것처럼 보이지만 실제로는 전체 페이지가 다시 로드되지 않는다.

history.replaceState
현재 히스토리 엔트리를 새로운 상태로 대체 (뒤로가기 X) 페이지를 다시 로드하지 않고 현재 페이지의 URL을 바꿀 때

  1. 사이트 렌더링에 필요한 <body/> 내부의 내용을 모두 자바스크립트 코드로 삽입한 이후에 렌더링을 한다.
  2. 페이지 전환 시 자바스크립트에서 다음 페이지의 렌더링에 필요한 정보만 HTTP요청 등으로 가져온 후, 그 결과를 바탕으로 body에 DOM을 추가, 수정, 삭제한다.
  3. 최초에 서버에서 최소한의 데이터를 불러온 이후에는 이미 가지고 있는 자바스크립트 리소스와 브라우저 API를 기반으루 모든 작동이 이뤄진다.

단점: 최초에 로딩해야 할 자바스크립트 리소스가 커진다.
장점: 한번 로딩된 이후에는 서버를 거쳐 필요한 리소스를 받아올 일이 적어 사용자에게 훌륭한 UI/UX를 제공한다.

 

📍전통적인 방식의 애플리케이션과 싱글 페이지 애플리케이션의 작동 비교

  전통적인 방식의 애플리케이션(과거 SSR) 싱글 페이지 애플리케이션(SPA)
페이지 전환 새로운 페이지를 요청하고 HTML 페이지를 전체적으로 다시 로드 현재 페이지를 동적으로 다시 작성해 페이지 전환
사용자 경험 페이지 전환 시 전체 페이지가 새로 렌더링되어 다소 불편할 수 있음 빠른 화면 전환과 부드러운 사용자 경험 제공
네트워크 트래픽 페이지 간 이동 시 매번 서버에 요청하므로 네트워크 트래픽 증가 필요한 데이터만 비동기로 받아와 렌더링하므로 네트워크 트래픽 감소
초기 로딩 시간 페이지 전환 시 빠른 응답 가능 모든 정적 리소스를 최초에 다운로드해야 하므로 초기 로딩 시간이 길어질 수 있음

과거 SSR vs. 현재 SSR

  과거의 SSR  현재의 SSR
렌더링 방식 서버에서 전체 HTML 생성 프레임워크/라이브러리를 활용한 혼합 방식
초기 로딩 속도 빠름 빠름
SEO 적합성 높음 높음
사용자 경험 좋지 않음 (페이지 전체 로드) 개선됨 (클라이언트 측 라우팅 및 상태 관리)
서버 리소스 사용량 높음 낮음
확장성 낮음 높음
개발자 경험 제한적 향상됨 (프레임워크/라이브러리 활용)

📍싱글 페이지 렌더링 방식의 유행과 JAM스택의 등장

  • 과거 PHP나 JSP를 기반으로 대부분의 웹 애플리케이션이 만들어졌을 때는 거의 대부분의 렌더링이 서버사이드에서 이뤄졌다. (페이지를 요청하면 서버에서 완성된 HTML을 내려받고, 페이지 이동이 있으면 새로운 페이지를 서버에서 내려받는 방식)
  • 자바스크립트가 서서히 다양한 작업을 수행하게 되면서 CommonJS, AMD가 등장하고, 이후론 우리가 잘 알고 있는 React, vue, Angular가 등장했다.
  • 과거에는 자바스크립트에서 할 수 있는 일이 제한적이었기 때문에 대부분의 처리를 서버에서 해야 해서 LAMP(Linux, Apache, Mysql, PHP/Python)스택을 썼다.
  • JAM(Javascript, API, Markup)스택이 등장하고, 대부분의 작업을 자바스크립트에서 수행할 수 있었기 때문에 프론트엔드는 자바스크립트와 마크업을 미리 빌드해 두고 정적으로 사용자에게 제공하면 이후 작동은 모두 사용자의 클라이언트에서 실행되므로 서버 확장성 문제에서 좀 더 자유로워질 수 있었다.

✨서버 사이드 렌더링이란?

  • 최초에 사용자에게 보여줄 페이지를 서버에서 렌더링해 빠르게 사용자에게 화면을 제공하는 방식

SPA - 자바스크립트 번들에서 렌더링 SSR - 서버에서 렌더링 (안정적임)

 

📍서버사이드 렌더링의 장점

  1. 최초 페이지 진입이 비교적 빠르다.
  2. 검색 엔진과 SNS 공유 등 메타데이터 제공이 쉽다
    1. 페이지가 HTML 정보를 제공해 로봇이 이 HTML을 다운로드한다. 단, 다운로드만 하고 자바스크립트 코드는 실행하지 않는다.
    2. 다운로드한 HTML 페이지 내부의 오픈 그래프나 메타 태그 정보를 기반으로 페이지의 검색정보를 가져오고 이를 바탕으로 검색엔진에 저장한다.
    1. 검색 엔진 로봇이 페이지에 진입한다.
  3. 누적 레이아웃 이동이 적다.
    SSR은 요청이 완전히 완료된 이후에 완성된 페이지를 제공하므로 이런 문제에서 비교적 자유롭다.
    ❓누적 레이아웃 이동
    사용자에게 페이지를 보여준 이후에 뒤늦게 어떤 HTML정보가 추가되거나 삭제되어 마치 화면이 덜컥거리는 것과 같은 부정적인 사용자 경험
  4. 사용자의 디바이스 성능에 비교적 자유롭다.
  5. 보안에 좀 더 안전하다.

📍서버사이드 렌더링의 단점

  1. 소스코드를 작성할 때 항상 서버를 고려해야 한다. 브라우저 전역 객체인 window나 sesstionStorage를 쓰면 window is not defined라는 에러를 마주하게 된다. window 사용이 불가피하다면 해당 코드가 서버사이드에서 실행되지 않도록 처리해야 한다.
    typeof window !== 'undefined'
  2. 적절한 서버가 구축돼 있어야 한다.
  3. 서비스 지연에 따른 문제 서버에서 사용자에게 보여줄 페이지에 대한 렌더링 작업이 끝나기 전까지는 사용자에게 그 어떤 정보도 제공할 수 없다.

✨SPA와 SSR을 모두 알아야 하는 이유

📍서버 사이드 렌더링 역시 만능이 아니다

웹페이지에서 사용자에게 제공하고 싶은 내용이 무엇인지, 또 어떤 우선순위에 따라 페이지의 내용을 보여줄지를 잘 설계하는 것이 중요하다..

 

📍싱글 페이지 애플리케이션과 서버 사이드 렌더링 애플리케이션

  1. 가장 뛰어난 싱글 페이지 애플리케이션은 가장 뛰어난 멀티 페이지 애플리케이션보다 낫다. SPA는 페이지 전환이 빠르고 부럽고, 필요한 데이터만 비동기로 로드해서 전체적인 성능이 뛰어나다. MPA는 페이지 전환 시 전체 페이지를 다시 로드애햐한다.
  2. 평균적인 싱글 페이지 애플리케이션은 평균적인 멀티 페이지 애플리케이션보다 느리다.
  3. SPA는 모든 정적 리소스를 최초에 다운로드하기 때문에 초기로딩시간이 길다. MPA 는 페이지 전환시 필요한 리소스만 다운로드하기 때문에 초기로딩속도가 빠르다.

📍현재의 서버 사이드 렌더링

최초 웹사이트 진입 시에는 SSR로 서버에서 완성된 HTML을 제공받고, 이후 라우팅에서는 서버에서 내려받은 자바스크립트를 바탕으로 마치 싱글 페이지 애플리케이션처럼 작동한다.

SPA vs. SSR

  SPA  SSR
렌더링 클라이언트 측에서 렌더링 서버 측에서 렌더링
초기 로딩 속도 느림 빠름
SEO 성능 낮음 높음
개발 복잡도 낮음 높음
서버 부하 낮음 높음
사용자 경험 부드러운 사용자 경험 새로고침이 필요한 사용자 경험
캐싱 클라이언트 측 캐싱 서버 측 캐싱
보안 클라이언트 측 보안 서버 측 보안

4.2 서버 사이드 렌더링을 위한 리액트 API 살펴보기

✨renderToString

  • 인수로 넘겨받은 리액트 컴포넌트를 렌더링해 HTML 문자열로 반환하는 함수
  • 클라이언트에서 실행되지 않고 일단 먼저 완성된 HTML을 서버에서 제공할 수 있어 초기 렌더링에서 뛰어나다.
  • 클라이언트는 이후에 리액트 컴포넌트를 다시 hydration해 상호작용이 가능한 SPA로 전환된다.
  • div#root에 생성된 data-reactroot는 리액트 컴포넌트의 루트 엘리먼트가 무엇인지 식별하는 역할로, 추후 hydrate 함수에서 루트를 식별하는 기준이 된다.
// server.js
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';

const app = express();

app.get('*', (req, res) => {
	// react 컴포넌트를 HTML 문자열로 변환
  const appString = ReactDOMServer.renderToString(<App />);

  const html = `
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>React SSR Example</title>
      </head>
      <body>
        <div id="root">${appString}</div>
        <script src="/bundle.js"></script>
      </body>
    </html>
  `;

  res.send(html);
});

app.listen(3000, () => {
  console.log('Server is running on <http://localhost:3000>');
});

// 서버-side 렌더링된 HTML
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React SSR Example</title>
  </head>
  <body>
    <div id="root" data-reactroot> // !!
      <div>
        <h1>Hello, React SSR!</h1>
      </div>
    </div>
    <script src="/bundle.js"></script>
  </body>
</html>

✨renderToStaticMarkup

  • HTML문자열을 만들지만, reactroot같은 추가적인 DOM 속성을 만들지 않는다. → 😲HTML 크기를 아주 약간이라도 줄일 수 있다.
  • hydrate를 수행하지 않는다는 가정하에 순수한 HTML만 반환한다.
  • 블로그 글이나 상품 약관 정보와 같이 아무런 브라우저 액션이 없는 정적인 내용만 필요한 경우에 유용하다.

✨renderToNodeStream

  • React 컴포넌트를 스트리밍 방식으로 HTML로 렌더링한다.
  • 브라우저에서 사용하는 것이 완전 불가능하다.
  • Node.js나 Deno, Bun같은 서버환경에서만 사용할 수 있다.
  • 큰 크기의 데이터를 청크단위로 분리해 순차적으로 처리할 수 있다.

❓스트림 큰 데이터를 다룰 때 데이터를 청크로 분할해 조금씩 가져오는 방식

만약 스트림 대신 renderToString을 사용했다면 HTTP응답은 거대한 HTML파일이 완성될 때까지 기다려야 할 것이다.

❓FCP(First Contentful Paint) 사용자가 페이지를 요청한 후 첫 번째 콘텐츠 요소가 렌더링되는 시간을 측정하는 지표 서버에서 페이지를 미리 렌더링하여 HTML을 전송하면, 클라이언트에서 빠르게 첫 번째 콘텐츠를 표시할 수 있다.

❓TTI(Time to Interactive) 사용자가 페이지와 상호 작용할 수 있게 되는 시간을 측정하는 지표 Next.js의 코드 분할 기능을 통해 TTI 성능이 향상된다. 코드 분할을 통해 필요한 JavaScript 파일만 로드하여, 페이지가 빠르게 상호 작용 가능한 상태가 된다.

✨renderToStaticNodeStream

  • renderToNodeStream과 비슷하지만, React의 데이터 속성(예: data-reactroot) 없이 순수한 HTML을 스트리밍한다.
  • hydrate를 할 필요가 없는 순서 HTML 결과물이 필요할 때 사용한다.

✨hydrate

  • 서버에서 렌더링된 HTML문서와 클라이언트 측 JS코드를 연결하는 과정 → 빠른 초기 로딩 속도
  • renderToString과 renderToNodeStream으로 생성된 HTML 컨텐츠에 자바스크립트 핸들러나 이벤트를 붙이는 역할을 한다.
  • 별도의 인수로 HTML 요소를 넘겨서 해당 컴포넌트의 렌더링과 이벤트 핸들러를 붙이는 작업까지 한 번에 수행하는 클라이언트의 render와는 다르게, hydrate는 이미 렌더링된 HTML이 있다는 가정하에 작업이 수행되고 이 렌더링된 HTML을 기준으로 이벤트를 붙이는 작업만 실행한다.

4.3 Next.js 톺아보기

✨Next.js란?

  • 풀스택 웹 애플리케이션을 구축하기 위한 리액트 기반 프레임워크
  • Next.js의 페이지 구조(실제 디렉터리 구조)가 곧 URL로 변환되는 것은 react-page에서 이미 라우팅을 위해 구현해 놓은 기능으로, Next.js도 동일하게 디렉터리 기반 라우팅을 서비스하고 있다.

✨Next.js 시작하기

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true
}

module.exports = nextConfig
  • reactStrictMode: 리액트의 엄격모드와 관련된 옵션. 리액트 애플리케이션 내부에서 잠재적인 문제를 개발자에게 알리기 위한 도구.
  • swcMinify: SWC를 사용해 번들링과 컴파일을 더욱 빠르게 수행한다. 페이지 로드 시간과 전반적인 성능을 향상시킬 수 있다.

📍Next 기본 파일 및 폴더 구조

Getting Started: Project Structure

 

Getting Started: Project Structure | Next.js

A list of folders and files conventions in a Next.js project

nextjs.org

 

📙pages/_app.tsx

  • 에러 바운더리를 사용해 애플리케이션 전역에서 발생하는 에러 처리
  • reset.css같은 전역 css 선언
  • 모든 페이지에 공통으로 사용 또는 제공해야 하는 데이터 제공

이 파일의 render() 내부에 콘솔을 찍으면 Next 실행한 터미널에 기록된다.
→ 최초에는 SSR을, 이후에는 클라이언트에서 _app.tsx의 렌더링이 실행된다.

 

📙pages/_document.tsx

  • HTML을 초기화 하는 곳
  • <html>, <body> 등 전체 페이지의 구조와 메타 태그, 외부 스크립트, 글꼴 등을 설정

_app.tsx는 Next.js를 초기화하는 파일로, Next.js 설정과 관련된 코드를 모아두는 곳이며 서버와 클라이언트 모두에서 렌더링 될 수 있다.

_document.tsx는 Next.js로 만드는 웹사이트의 뼈대가 되는 HTML 설정과 관련된 코드를 추가하는 곳이며, 반드시 서버에서 렌더링된다.

 

📙pages/_error.tsx

  • Next.js 프로젝트 전역에서 발생하는 에러 처리
  • 404 에러를 포함한 모든 종류의 에러에 대한 커스텀 페이지
function Error({ statusCode }) {
  return (
    <p>
      {statusCode
        ? `An error ${statusCode} occurred on server`
        : 'An error occurred on client'}
    </p>
  )
}
 
Error.getInitialProps = ({ res, err }) => {
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404
  return { statusCode }
}
 
export default Error

 

📙pages/404.tsx

  • 404페이지 정의할 수 있는 파일
  • 만들지 않으면 Next.js에서 기본으로 제공하는 404페이지가 뜸

📙pages/500.tsx

  • 에러를 핸들링하는 페이지
  • _error.tsx와 500.tsx가 있다면 500이 먼저 실행된다.
  • 서버 오류(500 에러) 전용 커스텀 페이지
export default function Custom500() {
  return <h1>500 - Server-side error occurred</h1>
}

 

📙pages/index.tsx

  • Pages Router에는 페이지 개념을 기반으로 구축된 파일 시스템 기반 라우터가 있다.
  • 파일이 디렉터리에 추가되면 pages자동으로 경로로 사용할 수 있다.

pages/index.js→ /

pages/blog/index.js → /blog

pages/blog/first-post.js → /blog/first-post

pages/dashboard/settings/username.js→ /dashboard/settings/username

pages/posts/[id].js → posts/1 , posts/2

pages/[...props].tsx → /foo, /bar, /foo/bar

Route Example URL params
pages/shop/[...slug].js /shop/a { slug: ['a'] }
pages/shop/[...slug].js /shop/a/b { slug: ['a', 'b'] }
pages/shop/[...slug].js /shop/a/b/c { slug: ['a', 'b', 'c'] }

📍서버 라우팅과 클라이언트 라우팅의 차이

  • Next.js는 SSR을 수행하지만 동시에 SPA와 같이 클라이언트 라우팅도 수행한다.
  • 클라이언트에서 필요한 자바스크립트만 불러온 뒤 라우팅하는 클라이언트 라우팅/렌더링 방식으로 작동한다.
// 폴더 구조
pages/
├── index.tsx
├── about.tsx
└── contact.tsx
// pages/index.tsx
// 사용자가 처음으로 localhost:3000에 접속하면 이 파일을 SSR해 HTML을 반환한다.
// 브라우저는 이 HTML을 렌더링하고 Next.js는 필요한 초기 JS를 로드한다.
import Link from 'next/link';

const Home = () => (
  <div>
    <h1>Home Page</h1>
    <nav>
      <ul>
        <li>
	        // about 클릭시 Next.js는 서버 요청 없이 about 컴포넌트를 로드한다.
	        // 이 과정에서 필요한 JavaScript만 불러와서 렌더링한다.
          <Link href="/about">
            <a>About</a>
          </Link>
        </li>
        <li>
	        // contact 클릭시 Next.js는 서버 요청 없이 contact 컴포넌트를 로드한다.
	        // 이 과정에서 필요한 JavaScript만 불러와서 렌더링한다.
          <Link href="/contact">
            <a>Contact</a>
          </Link>
        </li>
      </ul>
    </nav>
  </div>
);

export default Home;
// pages/about.tsx
// 필요한 초기 JS를 로드해 렌더링한다.
const About = () => (
  <div>
    <h1>About Page</h1>
    <p>This is the about page.</p>
  </div>
);

export default About;
// pages/contact.tsx
// 필요한 초기 JS를 로드해 렌더링한다.
const Contact = () => (
  <div>
    <h1>Contact Page</h1>
    <p>This is the contact page.</p>
  </div>
);

export default Contact;

Next.js는 페이지 컴포넌트를 청크(chunk)로 나누어 필요할 때만 로드하는 코드 스플리팅을 자동으로 처리한다. 이 방식은 Next.js의 기본 동작으로, 클라이언트 사이드 라우팅과 코드 스플리팅을 통해 애플리케이션의 성능을 최적화한다. 이를 통해 초기 로딩 시간을 단축하고, 사용자 경험을 향상시킬 수 있다.

Next.js에서 내부 페이지 이동 시 규칙 지켜야 할 것!

  1. <a> 대신 <Link> 쓰기
  2. window.location.push 대신 router.push 쓰기

📍페이지에서 getServerSideProps를 제거하면 어떻게 될까?

❓getServerSideProps
Next.js에서 서버 사이드 렌더링(SSR)을 위해 사용하는 함수. 이 함수를 사용하면 페이지를 요청할 때마다 서버에서 데이터를 가져와서 페이지를 렌더링한다.

 

⭕getServerSideProps

서버 사이드 런타임 체크가 되어있다.

❌getServerSideProps

빌드 크기가 약간 줄고, 서버 사이드 렌더링이 필요없는 정적인 페이지로 분류된다.

  • getServerSideProps가 없으면 서버에서 실행하지 않아도 되는 페이지로 처리하고 typeof window의 처리를 모두 object로 바꾼 다음, 빌드 시점에 미리 트리쉐이킹을 해버린다.
    • getServerSideProps 제거: 페이지가 서버에서 실행되지 않고 클라이언트에서만 실행된다.
    • typeof window 체크: 클라이언트 사이드에서만 실행되는 코드를 구분할 수 있다.
    • 트리 셰이킹: 빌드 시점에 사용되지 않는 코드를 제거하여 최종 번들을 최적화한다.
      ❓트리 셰이킹 빌드 시점에 사용되지 않는 코드를 제거하여 최종 번들을 최적화하는 과정

✨Data Fetching

pages/ 폴더에 있는 라우팅이 되는 파일에서만 사용할 수 있으며, 예약어로 지정되어 반드시 정해진 함수명으로 export를 사용해 함수를 파일 외부로 내보내야 한다.

 

📍getStaticPaths와 getStaticProps

  • 어떠한 페이지를 CMS(Contents Management System)나 블로그, 게시판과 같이 사용자와 관계없이 정적으로 결정된 페이지를 보여주고자 할 때 사용되는 함수
  • 둘이 함께 있어야 사용할 수 있다.

getStaticPaths: 빌드 시 실행되어 어떤 동적 경로를 미리 생성할지 결정함

getStaticProps: 빌드 시 각 경로에 대해 실행되어 해당 경로에 필요한 데이터를 가져온다.

→ 두 함수는 함께 사용되어 동적 라우트를 가진 페이지를 정적 생성(SSG)할 때 유용하다.

// 프로젝트 구조
pages/
├── posts/
│   └── [id].tsx
├── index.tsx
// pages/posts/[id].tsx
import { GetStaticPaths, GetStaticProps } from 'next';

interface PostProps {
  post: {
    id: string;
    title: string;
    content: string;
  };
}

const Post = ({ post }: PostProps) => (
  <div>
    <h1>{post.title}</h1>
    <p>{post.content}</p>
  </div>
);

export const getStaticPaths: GetStaticPaths = async () => {
  // 예를 들어, 외부 API로부터 게시물 목록을 가져옴
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  // 각 게시물의 id를 기반으로 경로를 생성
  const paths = posts.map((post: { id: string }) => ({
    params: { id: post.id },
  }));

  return {
    paths,
    fallback: false, // 빌드 시 정의되지 않은 경로는 404 페이지를 반환
  };
};

export const getStaticProps: GetStaticProps = async (context) => {
  const { id } = context.params!;
  // 예를 들어, 외부 API로부터 특정 게시물 데이터를 가져옴
  const res = await fetch(`https://api.example.com/posts/${id}`);
  const post = await res.json();

  return {
    props: {
      post,
    },
  };
};

export default Post;

 

❓fallback 미리 빌드해야 할 페이지가 너무 많은 경우에 사용 가능하다.

true: 사용자가 미리 빌드하지 않은 페이지에 접근할 경우 빌드되기 전까지 fallback 컴포넌트를 보여주고, 빌드 완료 이후에 해당 페이지를 보여준다.
blocking: 별도의 로딩과 같은 처리를 하지 않고, 단순히 빌드가 완료될 때까지 사용자를 기다리게 하는 옵션.

 

📍getServerSideProps

서버에서 실행되는 함수고, 해당 함수가 있다면 무조건 페이지 진입 전에 이 함수를 실행한다.

import { GetServerSideProps } from 'next';

interface HomeProps {
  data: {
    message: string;
  };
}

const Home = ({ data }: HomeProps) => (
  <div>
    <h1>Server Side Rendered Page</h1>
    <p>{data.message}</p>
  </div>
);

export const getServerSideProps: GetServerSideProps = async (context) => {
  // 서버에서 실행되어 데이터를 패치
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

	// 패치된 데이터는 props로 페이지 컴포넌트에 전달
  return {
    props: {
      data,
    },
  };
};

export default Home;
  • 어떤 조건에 따라 다른 페이지로 보내고 싶다면 redirect를 사용할 수 있다.

📍getInitialProps

  • getStaticProps나 getServerSideProps가 나오기 전에 사용할 수 있었던 유일한 페이지 데이터 불러오는 수단
  • 라우팅에 따라서 서버와 클라이언트 모두에게 실행 가능한 메서드다.
// 페이지가 처음 요청될 때 서버에서 getInitialProps가 실행된다.
// 서버에서 데이터를 패칭해 페이지 컴포넌트에 전달한다.
import React from 'react';
import { NextPage, NextPageContext } from 'next';

interface HomeProps {
  data: {
    message: string;
  };
}

const Home: NextPage<HomeProps> = ({ data }) => (
  <div>
    <h1>Page with getInitialProps</h1>
    <p>{data.message}</p>
  </div>
);

Home.getInitialProps = async (ctx: NextPageContext) => {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

	// 객체 반환 (이 객체가 곧 props로 전달됨)
	// getInitialProps가 서버와 클라이언트에서 모두 실행될 수 있기 때문에, 
	// Next.js가 이 반환 값을 적절하게 해석해 props로 전달한다.
  return { data };
};

export default Home;

✨next.config.js 살펴보기

// next.config.js

module.exports = {
  reactStrictMode: true,
  poweredByHeader: false,
  basePath: '/myapp',
  swcMinify: true,
  assetsPrefix: '<https://cdn.example.com>',

  redirects: async () => {
    return [
      {
        source: '/old-path', // 리다이렉션 소스 경로
        destination: '/new-path', // 리다이렉션 대상 경로
        permanent: true, // 영구 리다이렉션 여부 (301 상태 코드)
      },
      {
        source: '/another-old-path',
        destination: '/another-new-path',
        permanent: false, // 임시 리다이렉션 여부 (302 상태 코드)
      },
    ];
  },
};

  • reactStrictMode
    • true로 설정하면 리액트의 엄격 모드가 활성화
    • 엄격 모드는 리액트 컴포넌트에서 잠재적인 문제를 감지하고 경고를 표시한다.
    • 개발 단계에서 유용하며, 애플리케이션의 안정성을 높이는 데 도움이 된다.
  • poweredByHeader
    • false로 설정하면 Next.js가 생성한 페이지에서 X-Powered-By 헤더를 제거한다.
    • 보안을 강화하고 서버 정보를 숨기는 데 유용하다.
    • 기본적으로 Next.js는 X-Powered-By: Next.js 헤더를 추가한다.
  • basePath
    • 사이트의 모든 URL 앞에 기본 경로를 추가한다.
    • 예를 들어, basePath: '/myapp'로 설정하면 /myapp/page와 같은 경로로 접근할 수 있다.
    • 기본 경로를 설정하면 여러 Next.js 애플리케이션을 같은 도메인 하위 경로에 배포할 때 유용하다.
  • swcMinify
    • true로 설정하면 SWC 컴파일러를 사용하여 JavaScript 코드를 최소화한다.
    • SWC는 고성능 JavaScript/TypeScript 컴파일러로, Babel보다 빠르다.
    • 빌드 속도를 높이고 결과 코드의 크기를 줄이는 데 도움이 된다.
  • assetsPrefix
    • 외부 정적 자산에 대한 경로를 설정한다.
    • 예를 들어, assetsPrefix: '<https://cdn.example.com>'으로 설정하면 Next.js는 정적 자산을 이 CDN 경로에서 로드한다. → 자산을 외부 CDN에서 제공하여 로드 속도를 높이는 데 유용하다.
  • redirects
    • 리다이렉션 규칙을 설정한다.
    • source는 리다이렉션 소스 경로를 지정하고, destination은 리다이렉션 대상 경로를 지정한다.
    • permanent가 true이면 영구 리다이렉션(HTTP 상태 코드 301)을 의미하며, false이면 임시 리다이렉션(HTTP 상태 코드 302)을 의미한다.
    • 비동기 함수로 설정하여 여러 리다이렉션 규칙을 정의할 수 있다.

📍Next.js는 이미지를 최적화할 수 있는 next/image, 서드파티 스크립트를 최적화해서 불러올 수 있는 next/script 등 다양한 기능이 있다.