ReactNextCentral

Next.js로의 전환과 심화

Published on
리액트에서 Next.js로, Next.js 설치하기, 서버와 클라이언트 컴포넌트
Table of Contents

8장: 리액트에서 Next.js로

지금까지 리액트로 시작하는 방법에 대해 알아봤습니다. 최종 코드는 다음과 같습니다. 여기서 시작하는 경우 이 코드를 코드 에디터의 index.html 파일에 붙여넣으세요.

index.html
<html>
  <body>
    <div id="app"></div>
 
    <script src="https://unpkg.com/react@18/umd/react.development.js" />
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" />
    <script src="https://unpkg.com/@babel/standalone/babel.min.js" />
 
    <script type="text/jsx">
      const app = document.getElementById("app")
 
      function Header({ title }) {
        return <h1>{title ? title : "기본 제목"}</h1>
      }
 
      function HomePage() {
        const names = ["에이다 러브레이스", "그레이스 호퍼", "마거릿 해밀턴"]
 
        const [likes, setLikes] = React.useState(0)
 
        function handleClick() {
          setLikes(likes + 1)
        }
 
        return (
          <div>
            <Header title="개발. 미리보기. 출시." />
            <ul>
              {names.map((name) => (
                <li key={name}>{name}</li>
              ))}
            </ul>
 
            <button onClick={handleClick}>좋아요 ({likes})</button>
          </div>
        )
      }
 
      const root = ReactDOM.createRoot(app);
      root.render(<HomePage />);
    </script>
  </body>
</html>

지난 몇 장에서는 컴포넌트, 프롭스, 상태라는 세 가지 필수 리액트 개념을 소개했습니다. 이러한 기초를 잘 이해하는 것은 리액트 애플리케이션을 구축하는 데 도움이 될 것입니다.

리액트 학습에 있어 가장 좋은 방법은 실제로 구축해보는 것입니다. <script>와 지금까지 배운 내용을 사용하여 기존 웹사이트에 작은 컴포넌트를 추가함으로써 리액트를 점진적으로 적용할 수 있습니다. 그러나 많은 개발자들은 리액트가 가능하게 하는 사용자 및 개발자 경험을 충분히 가치 있게 여겨 전체 프론트엔드 애플리케이션을 리액트로 작성하는 것을 선호합니다.

리액트에서 Next.js로

리액트는 UI 구축에 뛰어나지만, UI를 완전히 기능하는 확장 가능한 애플리케이션으로 독립적으로 구축하는 데는 일정한 작업이 필요합니다. 서버 및 클라이언트 컴포넌트와 같은 새로운 리액트 기능도 프레임워크를 요구합니다. 좋은 소식은 Next.js가 많은 설정과 구성을 처리하며 리액트 애플리케이션을 구축하는 데 도움이 되는 추가 기능을 가지고 있다는 것입니다.

다음으로, 예제를 리액트에서 Next.js로 마이그레이션하고, Next.js의 작동 방식을 논의하며, 서버 및 클라이언트 컴포넌트 간의 차이점을 소개할 것입니다.


9장: Next.js 설치하기

프로젝트에서 Next.js를 사용할 때 더 이상 react와 react-dom 스크립트를 unpkg.com에서 로드할 필요가 없습니다. 대신 npm이나 선호하는 패키지 관리자를 사용해 이러한 패키지를 로컬에 설치할 수 있습니다.

참고: Next.js를 사용하려면 컴퓨터에 Node.js 버전 18.17.0 이상이 설치되어 있어야 합니다(최소 버전 요구 사항 참조), 여기에서 다운로드할 수 있습니다.

이를 위해 index.html 파일과 같은 디렉토리에 package.json이라는 새 파일을 만들고 빈 객체 {}를 넣으세요.

package.json
{}

터미널에서 프로젝트의 루트에서 다음 명령어를 실행하세요.

터미널
npm install react@latest react-dom@latest next@latest

설치가 완료되면 package.json 파일 내에 프로젝트 의존성이 나열되어 있어야 합니다.

package.json
{
  "dependencies": {
    "next": "^14.0.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

위에 표시된 것보다 더 늦은 버전에 있다고 걱정할 필요 없습니다. next, react, react-dom 패키지가 설치되어 있다면 문제없습니다.

package-lock.json 파일이라는 새 파일도 눈에 띌 텐데, 각 패키지의 정확한 버전에 대한 자세한 정보가 들어 있습니다.

index.html 파일로 돌아가서 다음 코드를 삭제하세요.

  • <html><body> 태그.
  • app이라는 id의 <div> 요소.
  • NPM을 사용해 설치했으므로 react 및 react-dom 스크립트.
  • Next.js가 JSX를 브라우저가 이해할 수 있는 유효한 자바스크립트로 변환하는 컴파일러를 가지고 있으므로 Babel 스크립트.
  • <script type="text/jsx"> 태그.
  • document.getElementById()ReactDom.createRoot() 메서드.
  • React.useState(0) 함수의 리액트 부분

위의 줄을 삭제한 후 파일 상단에 다음 import를 추가하세요.

index.html
import { useState } from 'react';

코드는 다음과 같아야 합니다.

index.html
import { useState } from 'react';
 
function Header({ title }) {
  return <h1>{title ? title : '기본 제목'}</h1>;
}
 
function HomePage() {
  const names = ['에이다 러브레이스', '그레이스 호퍼', '마거릿 해밀턴'];
 
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return (
    <div>
      <Header title="개발. 미리보기. 출시." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
 
      <button onClick={handleClick}>좋아요 ({likes})</button>
    </div>
  );
}

HTML 파일에 남아 있는 코드는 JSX뿐이므로 파일 유형을 .html에서 .js 또는 .jsx로 변경할 수 있습니다.

첫 페이지 만들기

Next.js는 파일 시스템 라우팅을 사용합니다. 즉, 애플리케이션의 라우트를 코드로 정의하는 대신 폴더와 파일을 사용할 수 있습니다.

Next.js에서 첫 페이지를 만드는 방법은 다음과 같습니다.

  • app이라는 새 폴더를 만들고 index.js 파일을 그 안으로 이동하세요.
  • index.js 파일을 page.js로 이름을 변경하세요. 이 파일이 애플리케이션의 메인 페이지가 될 것입니다.
  • <HomePage> 컴포넌트에 export default를 추가하여 Next.js가 페이지의 메인 컴포넌트로 렌더링할 컴포넌트를 구별할 수 있도록 도와주세요.
app/page.js
import { useState } from 'react';
 
function Header({ title }) {
  return <h1>{title ? title : '기본 제목'}</h1>;
}
 
export default function HomePage() {
  // ...
}

개발 서버 실행하기

다음으로 개발 중인 새 페이지의 변경 사항을 볼 수 있도록 개발 서버를 실행해봅시다. package.json 파일에 "next dev" 스크립트를 추가하세요:

package.json
{
  "scripts": {
    "dev": "next dev"
  },
  "dependencies": {
    "next": "^14.0.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

터미널에서 npm run dev를 실행하여 어떤 일이 일어나는지 확인하세요. 두 가지 사항을 주목할 것입니다.

  • localhost:3000으로 이동하면 다음과 같은 오류가 표시됩니다. 오류 Next.js 오류 메시지: useState가 필요한 컴포넌트를 가져오고 있습니다. 이는 클라이언트 컴포넌트에서만 작동합니다... 이는 Next.js가 서버에서 렌더링할 수 있게 하는 새로운 기능인 리액트 서버 컴포넌트를 사용하기 때문입니다. 서버 컴포넌트는 useState를 지원하지 않으므로 대신 클라이언트 컴포넌트를 사용해야 합니다.

  • 다음 장에서는 서버 컴포넌트와 클라이언트 컴포넌트의 주요 차이점을 논의하고 이 오류를 수정할 것입니다.

  • app 폴더 안에 layout.js라는 새 파일이 자동으로 생성되었습니다. 이것은 애플리케이션의 메인 레이아웃입니다. 모든 페이지(예: 내비게이션, 푸터 등)에 공통으로 사용되는 UI 요소를 추가하는 데 사용할 수 있습니다.

app/layout.js
export const metadata = {
  title: 'Next.js',
  description: 'Next.js에 의해 생성됨',
};
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

요약

지금까지의 마이그레이션을 보면 이미 Next.js를 사용하는 이점을 느끼기 시작했을 것입니다.

  • 리액트와 Babel 스크립트를 제거했습니다; 더 이상 신경 쓰지 않아도 되는 복잡한 툴링과 설정에 대한 맛보기입니다.
  • 첫 페이지를 만들었습니다.

10장: 서버와 클라이언트 컴포넌트

서버와 클라이언트 컴포넌트의 작동 방식을 이해하려면 두 가지 기본적인 웹 개념에 익숙해지는 것이 도움이 됩니다.

  • 애플리케이션 코드가 실행될 수 있는 환경: 서버와 클라이언트
  • 서버와 클라이언트 코드를 분리하는 네트워크 경계

서버와 클라이언트 환경

웹 애플리케이션에서:

왼쪽에 브라우저와 오른쪽에 서버가 있으며, 네트워크 경계로 분리된 다이어그램

  • 클라이언트는 사용자의 기기에 있는 브라우저를 의미하며 서버에 애플리케이션 코드에 대한 요청을 보냅니다. 그런 다음 서버로부터 받은 응답을 사용자가 상호작용할 수 있는 인터페이스로 변환합니다.
  • 서버는 데이터 센터의 컴퓨터를 의미하며 애플리케이션 코드를 저장하고 클라이언트로부터 요청을 받아 일부 계산을 수행한 뒤 적절한 응답을 돌려보냅니다.

각 환경은 자체적인 능력과 제약을 가지고 있습니다. 예를 들어, 렌더링과 데이터 가져오기를 서버로 이동함으로써 클라이언트로 전송되는 코드의 양을 줄여 애플리케이션의 성능을 개선할 수 있습니다. 하지만 앞서 배운 것처럼 UI를 상호작용 가능하게 만들려면 클라이언트에서 DOM을 업데이트해야 합니다.

따라서 서버와 클라이언트에서 작성하는 코드는 항상 같지 않습니다. 특정 작업(예: 데이터 가져오기 또는 사용자 상태 관리)은 다른 환경보다 한 환경에 더 적합할 수 있습니다.

네트워크 경계

네트워크 경계는 서로 다른 환경을 분리하는 개념적인 경계입니다.

리액트에서는 컴포넌트 트리에서 네트워크 경계를 어디에 둘지 선택할 수 있습니다. 예를 들어, 서버에서 사용자의 게시물을 데이터를 가져와 렌더링할 수 있습니다(서버 컴포넌트 사용), 그리고 각 게시물에 대한 상호작용 가능한 LikeButton을 클라이언트에서 렌더링할 수 있습니다(클라이언트 컴포넌트 사용).

마찬가지로, 서버에서 렌더링되고 페이지간에 공유되는 Nav 컴포넌트를 만들 수 있지만, 링크에 활성 상태를 표시하고 싶다면 클라이언트에서 링크 목록을 렌더링할 수 있습니다.

네트워크 경계 레이아웃이 Nav, Page, Footer라는 3개의 자식 컴포넌트를 가진 컴포넌트 트리를 보여줍니다. Page 컴포넌트에는 PostsLikeButton이라는 2개의 자식이 있습니다. Posts 컴포넌트는 서버에서 렌더링되고 LikeButton 컴포넌트는 클라이언트에서 렌더링됩니다. 내부적으로 컴포넌트는 서버 모듈 그래프(또는 트리)와 클라이언트 모듈 그래프(또는 트리)라는 두 가지 모듈 그래프로 분할됩니다. 서버 모듈 그래프는 서버에서 렌더링되는 모든 서버 컴포넌트를 포함하고 클라이언트 모듈 그래프는 모든 클라이언트 컴포넌트를 포함합니다.

서버 컴포넌트가 렌더링된 후 리액트 서버 컴포넌트 페이로드(RSC)라고 불리는 특별한 데이터 형식이 클라이언트로 전송됩니다. RSC 페이로드에는 다음이 포함됩니다.

  1. 서버 컴포넌트의 렌더링 결과
  2. 클라이언트 컴포넌트가 렌더링될 위치에 대한 플레이스홀더(또는 구멍)와 그들의 자바스크립트 파일에 대한 참조

리액트는 이 정보를 사용하여 서버와 클라이언트 컴포넌트를 통합하고 클라이언트에서 DOM을 업데이트합니다.

이게 어떻게 작동하는지 살펴봅시다.

클라이언트 컴포넌트 사용하기

지난 장에서 배웠듯이 Next.js는 기본적으로 서버 컴포넌트를 사용합니다. 이는 애플리케이션의 성능을 향상시키고 추가적인 도입 단계를 필요로 하지 않는다는 의미입니다.

브라우저의 오류를 돌아보면, 서버 컴포넌트 내에서 useState를 사용하려고 한다는 경고를 Next.js가 주고 있습니다. 상호작용 가능한 "좋아요" 버튼을 클라이언트 컴포넌트로 이동시켜 이를 해결할 수 있습니다.

app 폴더 안에 like-button.js라는 새 파일을 만들고 LikeButton 컴포넌트를 내보냅니다.

app/like-button.js
export default function LikeButton() {}

page.js에서 <button> 요소와 handleClick() 함수를 새로운 LikeButton 컴포넌트로 이동시킵니다.

app/like-button.js
export default function LikeButton() {
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>좋아요 ({likes})</button>;
}

likes 상태와 import를 이동시킵니다.

app/like-button.js
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>좋아요 ({likes})</button>;
}

LikeButton을 클라이언트 컴포넌트로 만들기 위해 파일 상단에 리액트 use client 지시문을 추가하세요. 이는 리액트에게 컴포넌트를 클라이언트에서 렌더링하라고 알려줍니다.

app/like-button.js
'use client';
 
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>좋아요 ({likes})</button>;
}

page.js 파일로 돌아가 LikeButton 컴포넌트를 페이지에 가져옵니다.

app/page.js
import LikeButton from './like-button';
 
function Header({ title }) {
  return <h1>{title ? title : '기본 제목'}</h1>;
}
 
export default function HomePage() {
  const names = ['에이다 러브레이스', '그레이스 호퍼', '마거릿 해밀턴'];
 
  return (
    <div>
      <Header title="개발. 미리보기. 출시." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
      <LikeButton />
    </div>
  );
}

두 파일을 저장하고 브라우저에서 앱을 확인하세요. 이제 오류가 없으며 변경 사항을 저장할 때마다 브라우저가 자동으로 변경 사항을 반영하여 업데이트됩니다.

이 기능을 Fast Refresh라고 합니다. 수정한 내용에 대해 즉각적인 피드백을 제공하며 Next.js와 함께 사전 구성됩니다.

요약

요약하자면, 서버와 클라이언트 환경 및 각각을 사용해야 하는 경우에 대해 배웠습니다. 또한 Next.js가 기본적으로 성능을 향상시키기 위해 리액트 서버 컴포넌트를 사용하며 UI의 작은 부분을 상호작용 가능하게 만들기 위해 클라이언트 컴포넌트를 선택적으로 사용하는 방법에 대해서도 배웠습니다.