Next.js

younghyun·2022년 7월 12일
0

사용자 인터페이스

사용자가 화면에서 보고 상호 작용하는 요소

React

프로젝트에서 React를 사용하려면 unpkg.com 이라는 외부 웹사이트에서 두 개의 React 스크립트를 로드할 수 있음.

react

핵심 React 라이브러리

react-dom

React를 DOM과 함께 사용할 수 있게 해주는 DOM 전용 메서드 제공

<!-- index.html -->
<html>
  <body>
    <div id="app"></div>

    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

    <script type="text/javascript">
      const app = document.getElementById('app');
    </script>
  </body>
</html>

일반 JavaScript로 DOM을 직접 조작하는 대신 from ReactDOM.render()메서드를 사용하여 React에 요소 내에서 제목을 react-dom렌더링하도록 지시할 수 있음 .<h1>#app

<!-- index.html -->
<html>
  <body>
    <div id="app"></div>

    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

    <script type="text/javascript">
      const app = document.getElementById('app');
      ReactDOM.render(<h1>Develop. Preview. Ship. 🚀</h1>, app);
    </script>
  </body>
</html>

브라우저에서 이 코드를 실행하려고 하면 구문 오류가 발생.
<h1>...</h1>유효한 Javascript가 아니기 때문입니다 . 이 코드는 JSX

JSX

JSX는 친숙한 HTML 유사 구문 으로 UI를 설명할 수 있는 JavaScript용 구문 확장.
JSX의 좋은 점은 세 가지 JSX 규칙을 따르는 것 외에 HTML 및 JavaScript 외부에서 새로운 기호나 구문을 배울 필요가 없다는 것.
브라우저는 기본적으로 JSX를 이해하지 못하므로 JSX 코드를 일반 JavaScript로 변환하려면 Babel 과 같은 JavaScript 컴파일러가 필요.

JSX 문법 규칙
  1. 단일 루트 요소 반환
    구성 요소에서 여러 요소를 반환하려면 단일 상위 태그로 요소를 래핑.
<div>
  <h1>Hedy Lamarr's Todos</h1>
  <img 
    src="https://i.imgur.com/yXOvdOSs.jpg" 
    alt="Hedy Lamarr" 
    class="photo"
  >
  <ul>
    ...
  </ul>
</div>

이 빈 태그를 Fragment라고 함 . 프래그먼트를 사용하면 브라우저 HTML 트리에 흔적을 남기지 않고 항목을 그룹화할 수 있음.
JSX는 HTML처럼 보이지만 내부적으로는 일반 JavaScript 객체로 변환됨. 배열로 래핑하지 않고는 함수에서 두 객체를 반환할 수 없음. 이는 두 개의 JSX 태그를 다른 태그나 Fragment로 래핑하지 않고 반환할 수 없는 이유를 설명. ( 여러 JSX 태그를 Wrapping 해야 하는 이유 )

  1. 모든 태그 닫기
    SX에서는 태그가 명시적으로 닫혀야 함. <img>must be 와 같은 자동 닫힘 태그 <img />와 와 같은 래핑 태그는 <li>oranges로 작성되어야 함 <li>oranges</li>.

다음은 Hedy Lamarr의 이미지와 목록 항목이 닫힌 모습.

<>
  <img 
    src="https://i.imgur.com/yXOvdOSs.jpg" 
    alt="Hedy Lamarr" 
    class="photo"
   />
  <ul>
    <li>Invent new traffic lights</li>
    <li>Rehearse a movie scene</li>
    <li>Improve the spectrum technology</li>
  </ul>
</>
  1. 카멜 케이스 대부분
    SX는 JavaScript로 바뀌고 JSX로 작성된 속성은 JavaScript 객체의 키가 됨. 자신의 구성 요소에서 이러한 속성을 변수로 읽어들이고 싶을 때가 많음. 그러나 JavaScript에는 변수 이름에 대한 제한이 있음. 예를 들어 이름에 대시를 포함하거나 와 같은 예약어를 사용할 수 없음 class.

이것이 React에서 많은 HTML 및 SVG 속성이 camelCase로 작성되는 이유. 예를 stroke-width들어 strokeWidth. class는 예약어이므로 React에서는 해당 DOM 속성의className 이름을 따서 대신 작성.

<img 
  src="https://i.imgur.com/yXOvdOSs.jpg" 
  alt="Hedy Lamarr" 
  className="photo"
/>

그런 다음 브라우저에서 코드를 실행하여 올바르게 작동하는지 확인할 수 있음.

프로젝트에 Babel 추가

프로젝트에 Babel 추가
프로젝트에 Babel을 추가하려면 index.html파일에 다음 스크립트를 복사하여 붙여넣기

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

또한 스크립트 유형을 type=text/jsx.

<html>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- Babel Script -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/jsx">
      const app = document.getElementById('app');
      ReactDOM.render(<h1>Develop. Preview. Ship. 🚀</h1>, app);
    </script>
  </body>
</html>

방금 작성한 선언적 React 코드를 비교하면 다음 과 같음.

<script type="text/jsx">
  const app = document.getElementById("app")
  ReactDOM.render(<h1>Develop. Preview. Ship. 🚀</h1>, app)
</script>

이전 섹션 에서 작성한 명령형 JavaScript 코드

<script type="text/javascript">
  const app = document.getElementById('app');
  const header = document.createElement('h1');
  const headerContent = document.createTextNode('Develop. Preview. Ship. 🚀');
  header.appendChild(headerContent);
  app.appendChild(header);
</script>

React를 사용하여 많은 반복 코드를 줄일 수 있는 방법을 확인할 수 있음.

이것이 바로 React가 하는 일. React는 사용자를 대신하여 작업을 수행하는 재사용 가능한 코드 스니펫을 포함하는 라이브러리. 이 경우에는 UI를 업데이트 함.

구성 요소로 UI 구축

사용자 인터페이스는 구성요소 라고 하는 더 작은 빌딩 블록으로 나눌 수 있음.

구성 요소를 사용하면 독립적이고 재사용 가능한 코드 스니펫을 작성할 수 있음. 구성 요소를 LEGO 브릭 으로 생각하면 이러한 개별 브릭을 가져와 함께 결합하여 더 큰 구조를 형성할 수 있음. UI의 일부를 업데이트해야 하는 경우 특정 구성 요소 또는 브릭을 업데이트할 수 있음.

이 모듈성은 애플리케이션의 나머지 부분을 건드리지 않고도 구성 요소를 쉽게 추가, 업데이트 및 삭제할 수 있기 때문에 성장함에 따라 코드를 보다 유지 관리할 수 있음.

React 구성 요소의 좋은 점은 바로 JavaScript라는 것. JavaScript 관점에서 React 구성 요소를 작성하는 방법을 살펴봄.

Component 생성

React에서 컴포넌트는 함수. 태그 안에 script다음과 같은 함수를 작성 header.

<script type="text/jsx">
  const app = document.getElementById("app")


  function header() {
  }

  ReactDOM.render(<h1>Develop. Preview. Ship. 🚀</h1>, app)
</script>

컴포넌트는 UI 요소를 반환하는 함수 . 함수의 return 문 안에 JSX를 작성할 수 있음.

<script type="text/jsx">
  const app = document.getElementById("app")

  function header() {
     return (<h1>Develop. Preview. Ship. 🚀</h1>)
   }

  ReactDOM.render(, app)
</script>

이 구성 요소를 DOM에 렌더링하려면 메서드의 첫 번째 인수로 전달할 수 있음. ReactDOM.render().

<script type="text/jsx">

  const app = document.getElementById("app")

  function header() {
     return (<h1>Develop. Preview. Ship. 🚀</h1>)
   }


   ReactDOM.render(header, app)
</script>

하지만, 잠깐만. 브라우저에서 위의 코드를 실행하려고 하면 오류가 발생합니다. 이 작업을 수행하려면 다음 두 가지 작업을 수행해야함.

첫째, React 구성 요소는 일반 HTML 및 JavaScript와 구별하기 위해 대문자로 표시되어야함.

function Header() {
  return <h1>Develop. Preview. Ship. 🚀</h1>;
}

// Capitalize the React Component
ReactDOM.render(Header, app);

둘째, 꺾쇠 괄호와 함께 일반 HTML 태그를 사용하는 것과 같은 방식으로 React 구성 요소를 사용. <>

function Header() {
  return <h1>Develop. Preview. Ship. 🚀</h1>;
}

ReactDOM.render(<Header />, app);
Component 중첩

애플리케이션에는 일반적으로 단일 구성 요소보다 더 많은 콘텐츠가 포함. 일반 HTML 요소처럼 React 구성 요소를 서로 중첩 할 수 있음.

귀하의 예에서 다음과 같은 새 구성 요소를 만듦. HomePage

function Header() {
  return <h1>Develop. Preview. Ship. 🚀</h1>;
}
function HomePage() {
  return <div></div>;
}

ReactDOM.render(<Header />, app);

그런 다음 <Header><HomePage>구성 요소 안에 구성 요소를 중첩.

function Header() {
  return <h1>Develop. Preview. Ship. 🚀</h1>;
}

function HomePage() {
  return (
    <div>
      {/* Nesting the Header component */}
      <Header />
    </div>
  );
}

ReactDOM.render(<Header />, app);
Component Tree

이러한 방식으로 React Component를 중첩하여 구성 요소 트리를 형성할 수 있음.

예를 들어 최상위 Component는 , 및 Component를 HomePage보유할 수 있습니다 . 그리고 이러한 각 Component는 차례로 자체 하위 Component 등을 가질 수 있음. 예를 들어 Component에는 , 및 Component가 포함될 수 있음. .
<Header/>
<Article/>
<Footer/>
<Header/>
<Logo/>
<Title/>
<Navigation/>
이 모듈 형식을 사용하면 앱 내부의 여러 위치에서 Component를 재사용할 수 있음.

프로젝트에서 <HomePage>는 최상위 Component이므로 ReactDOM.render() 메서드에 전달할 수 있음 .

function Header() {
  return <h1>Develop. Preview. Ship. 🚀</h1>;
}

function HomePage() {
  return (
    <div>
      <Header />
    </div>
  );
}

ReactDOM.render(<HomePage />, app);
Displaying Data with Props

지금까지 Component를 재 사용하는 경우 <Header />두 번 모두 동일한 콘텐츠를 표시.

function Header() {
  return <h1>Develop. Preview. Ship. 🚀</h1>;
}

function HomePage() {
  return (
    <div>
      <Header />
      <Header />
    </div>
  );
}

하지만 다른 텍스트를 전달하고 싶거나 외부 소스에서 데이터를 가져오기 때문에 미리 정보를 알지 못하는 경우에는 어떻게 해야 할까

일반 HTML 요소에는 해당 요소의 동작을 변경하는 정보를 전달하는 데 사용할 수 있는 속성이 있음. 예를 들어 요소 src의 속성을 변경 <img>하면 표시되는 이미지가 변경됨. href태그 속성을 변경하면 <a>링크 대상이 변경됨.

같은 방식으로 일부 정보를 속성으로 React Component에 전달할 수 있음. 이들은 props라고 함.

JavaScript 함수와 유사하게 Component 동작 또는 화면에 렌더링될 때 가시적으로 표시되는 내용을 변경하는 사용자 지정 인수(또는 props)를 허용하는 Component 디자인할 수 있음. 그런 다음 이러한 props을 상위 Component에서 하위 Component로 전달할 수 있음.

참고: React에서 데이터는 구성 요소 트리 아래로 흐름. 이를 단방향 데이터 흐름 이라고 함. 다음 섹션에서 설명할 상태는 부모 구성 요소에서 자식 구성 요소로 props로 전달될 수 있음.

using props

Component 에서 HTML 특성을 전달하는 것처럼
Header Component에 title prop을 전달할 수 있음.

// function Header() {
//   return <h1>Develop. Preview. Ship. 🚀</h1>
// }

function HomePage() {
  return (
    <div>
      <Header title="React 💙" />
    </div>
  );
}

// ReactDOM.render(<HomePage />, app)

그리고 Child Component인 Header에 해당 props를 첫 번째 함수 매개 변수로 허용할 수 있음.

function Header(props) {
//   return <h1>Develop. Preview. Ship. 🚀</h1>
// }

// function HomePage() {
//   return (
//     <div>
//       <Header title="React 💙" />
//     </div>
//   )
// }

// ReactDOM.render(<HomePage />, app)

props 하면 제목 속성이 있는 객체 console.log() 임을 알 수 있음.

function Header(props) {
    console.log(props) // { title: "React 💙" }
//   return <h1>React 💙</h1>
// }

// function HomePage() {
//   return (
//     <div>
//       <Header title="React 💙" />
//     </div>
//   )
// }

// ReactDOM.render(<HomePage />, app)

props는 객체(상위 컴포넌트에서 하위 컴포넌트로 값을 전달하는 객체, 읽기 전용 정보)이므로 객체 구조 분해를 사용하여 함수 매개변수 내에서 props 값의 이름을 명시적으로 지정할 수 있음.

function Header({ title }) {
    console.log(title) // "React 💙"
//  return <h1>React 💙</h1>
// }

// function HomePage() {
//   return (
//     <div>
//       <Header title="React 💙" />
//     </div>
//   )
// }

// ReactDOM.render(<HomePage />, app)

<h1>그런 다음 태그 내용을 제목 변수로 바꿀 수 있음.

function Header({ title }) {
  console.log(title);
  return <h1>title</h1>;
}

브라우저에서 프로젝트를 열면 실제 "제목"이라는 단어가 표시되는 것을 볼 수 있음. 이는 React가 DOM에 일반 텍스트 문자열을 렌더링하려고 한다고 생각하기 때문.

이것이 JavaScript 변수임을 React에 표시하는 방법이 필요.

JSX에서 변수 사용

정의한 변수를 사용하려면 JSX 마크업 내에서 일반 JavaScript를 직접 작성할 수 있는 특수 JSX 구문인 중괄호를 사용할 수 있음.

// function Header({title}) {
//  console.log(title)
return <h1>{title}</h1>;
// }

중괄호는 "JSX 랜드"에 있을 때 "JavaScript 랜드"로 들어가는 방법으로 생각할 수 있음. 중괄호 안에 JavaScript 표현식 (단일 값으로 평가되는 것)을 추가할 수 있음. 예를 들어

  1. 점 표기법을 사용하는 객체 속성.
function Header(props) {
  return <h1>{props.title}</h1>;
}
  1. 템플릿 리터럴
function Header({ title }) {
  return <h1>{`Cool ${title}`}</h1>;
}
  1. 함수 반환 값
function createTitle(title) {
  if (title) {
    return title;
  } else {
    return 'Default title';
  }
}

function Header({ title }) {
  return <h1>{createTitle(title)}</h1>;
}
  1. 또는 삼항 연산자
function Header({ title }) {
  return <h1>{title ? title : 'Default Title'}</h1>;
}

이제 title prop에 모든 문자열을 전달할 수 있으며 삼항 연산자를 사용하여 구성 요소의 기본 사례를 설명

function Header({ title }) {
  return <h1>{title ? title : 'Default title'}</h1>;
}

function HomePage() {
  return (
    <div>
      <Header />
    </div>
  );
}

Component는 이제 애플리케이션의 다른 부분에서 재사용할 수 있는 title prop를 허용함. title을 변경하기만 하면 됨.

function HomePage() {
  return (
    <div>
      <Header title="React 💙" />
      <Header title="A new title" />
    </div>
  );
}
목록반복

배열 메서드를 사용하여 데이터를 조작하고 스타일은 동일하지만 다른 정보를 포함하는 UI 요소를 생성할 수 있음.
HomePage Component에 배열 추가.

function HomePage() {
  const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];

  return (
    <div>
      <Header title="Develop. Preview. Ship. 🚀" />
    </div>
  );
}

그런 다음 array.map()메서드를 사용하여 배열을 반복하고 화살표 함수를 사용하여 이름을 목록 항목에 매핑할 수 있음.

function HomePage() {
  const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];

  return (
    <div>
      <Header title="Develop. Preview. Ship. 🚀" />
      <ul>
        {names.map((name) => (
          <li>{name}</li>
        ))}
      </ul>
    </div>
  );
}

중괄호를 사용하여 "JavaScript" 및 "JSX" 영역 안팎으로 짜는 방법에 주목.

이 코드를 실행하면 React는 누락된 key소품에 대한 경고를 표시. 이는 React가 DOM에서 업데이트할 요소를 알기 위해 배열의 항목을 고유하게 식별할 무언가가 필요하기 때문.

이름은 현재 고유하므로 지금은 사용할 수 있지만 항목 ID와 같이 고유함이 보장된 이름을 사용하는 것이 좋음.

function HomePage() {
  const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];

  return (
    <div>
      <Header title="Develop. Preview. Ship. 🚀" />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
    </div>
  );
}

Next.js

SSR을 쉽게 구현하도록 도와주는 React 기반 프레임워크

왜 사용 하는가, 왜 생겼는가?
React가 가진 CSR 방식의 두 가지 문제점 해결
1. 검색 엔진 최적화 문제
2. 초기 화면 구성이 늦어지는 문제
Next.js는 SSR방식으로 SEO에 유리하기 때문에 사용함. Next.js는 Server에서 받은 사용자의 접속 요청을 초기에 SSR방식으로 렌더링 된 HTML을 보내고, 브라우저가 다운로드하고, JavaScript를 다운로드하고 React를 실행하기 때문에 SEO가 가능함. 초기 화면 구성도 빠름. 또한 다른 페이지로 이동할 경우 CSR방식으로 Server가 아닌 브라우저에서 처리함으로써 SPA장점도 유지할 수 있음.

이렇게 하면 수백 페이지가 있는 경우에도 홈페이지가 빠르게 로드됨.

요청한 페이지에 대한 코드만 로드하면 페이지가 격리됨. 특정 페이지에서 오류가 발생해도 애플리케이션의 나머지 부분은 계속 작동함.

또한 Next.js의 프로덕션 빌드에서 Link구성 요소가 브라우저의 뷰포트에 나타날 때마다 Next.js는 백그라운드에서 링크된 페이지에 대한 코드를 자동으로 미리 가져옴. 링크를 클릭하면 대상 페이지의 코드가 이미 백그라운드에 로드되고 페이지 전환이 거의 즉각적으로 이루어짐.

Next.js 작동 방식

Next.js 적용되는 방식

Next.js는 애플리케이션의 개발 및 생산 단계 모두에 대한 기능 제공.

  • 개발 단계에서 Next.js는 개발자와 애플리케이션 구축 경험을 최적화 함. TypeScriptESLint 통합 , 빠른 새로 고침 등과 같은 개발자 경험을 개선하기 위한 기능이 함께 제공됨.
  • 프로덕션 단계에서 Next.js는 최종 사용자와 애플리케이션 사용 경험을 최적화 함. 성능과 접근성을 높이기 위해 코드를 변환하는 것을 목표로 함

환경마다 고려 사항과 목표가 다르기 때문에 응용 프로그램을 개발에서 프로덕션으로 이동하려면 수행해야 할 작업이 많음. 예를 들어 애플리케이션 코드는 컴파일 , 번들 , 축소코드 분할이 필요.

Next.js 컴파일러

Next.js는 이러한 코드 변환 및 기본 인프라의 대부분을 처리하여 애플리케이션이 프로덕션으로 쉽게 전환되도록 함.

이것은 Next.js가 저수준 프로그래밍 언어인 Rust로 작성된 컴파일러 와 컴파일, 최소화, 번들링 등에 사용할 수 있는 플랫폼인 SWC를 가지고 있기 때문에 가능함.

컴파일(Compile)

한 언어로 된 코드를 다른 언어 또는 해당 언어의 다른 버전으로 출력하는 프로세스.

개발자는 JSX, TypeScript 및 최신 버전의 JavaScript와 같이 개발자에게 더 친숙한 언어로 코드를 작성함. 이러한 언어는 개발자의 효율성과 자신감을 향상시키지만 브라우저에서 이해할 수 있으려면 먼저 JavaScript로 컴파일 해야 함.

Next.js에서 컴파일은 개발 단계에서 코드를 편집할 때 발생하며 프로덕션을 위해 애플리케이션을 준비하는 빌드 단계의 일부로 발생.

Minifying

개발자는 사람의 가독성에 최적화된 코드를 작성. 이 코드에는 주석, 공백, 들여쓰기, 여러 줄 등 코드 실행에 필요하지 않은 추가 정보가 포함될 수 있음.

최소화는 코드 기능을 변경하지 않고 불필요한 코드 서식과 주석을 제거하는 프로세스. 목표는 파일 크기를 줄여 애플리케이션의 성능 향상 시키는 것.

Next.js에서 JavaScript 및 CSS 파일은 프로덕션을 위해 자동으로 축소됨.

Bundling

사용자가 웹 페이지를 방문할 때 파일에 대한 요청 수를 줄이기 위해 종속성 웹을 해결하고 파일(또는 모듈)을 브라우저에 최적화된 번들로 병합(또는 '패키징')하는 프로세스.

개발자는 응용 프로그램을 더 큰 응용 프로그램을 구축하는 데 사용할 수 있는 모듈, 구성 요소 및 기능으로 나눔. 이러한 내부 모듈과 외부 타사 패키지를 내보내고 가져오면 파일 종속성의 복잡한 웹이 생성됨.

Code Splitting

React를 빌드/배포하면 기본적으로 모든 js, css 파일이 하나의 파일로 번들링되며 하나의 큰 파일이 됨. 따로 webpack 설정을 해주지 않는다면, 특정 페이지에 필요없는 다른 파일들까지 번들링 되기 때문에 파일이 커지면 성능에 문제가 될 수도 있음. 또한, 한 줄의 js 코드만 수정해도 모든 JS코드를 새로 빌드해야하기 때문에 비효율적. 따라서, 파일을 분리하는 작업인 '코드 스플리팅'을 하는 것이 효율적.

예를 들어 페이지가 /main, /about, /post 이렇게 세 가지 페이지로 이루어진 SPA를 개발한다고 할 때 /main 페이지를 들어가는 동안 /about이나 /post 페이지 정보는 사용자에게 필요하지 않을 확률이 높다. 그러한 파일들을 분리하여 지금 사용자에게 필요한 파일만 불러올 수가 있다면 로딩도 빠르게 이루어지고 트래픽도 줄어 사용자 경험이 좋아질 수가 있다. 이와 같이 더 나은 사용자 경험을 위해 코드를 비동기적으로 로딩하는 방법이 있는데 코드 비동기 로딩의 대표적인 예시가 바로 코드 스플리팅이다. 출처: [React] 코드 스플리팅(Code Splitting) — 오웬의 개발 이야기

개발자는 일반적으로 애플리케이션을 서로 다른 URL에서 액세스할 수 있는 여러 페이지로 분할. 이러한 각 페이지는 응용 프로그램에 대한 고유한 진입점이 됨.

코드 분할은 애플리케이션의 번들을 각 진입점에 필요한 더 작은 청크로 분할하는 프로세스. 목표는 해당 페이지를 실행하는 데 필요한 코드만 로드하여 애플리케이션의 초기 로드 시간을 개선하는 것.

  • Next.js는 코드 분할을 기본적으로 지원. 디렉토리 내의 각 파일은 pages/빌드 단계에서 자체 JavaScript 번들로 자동으로 코드 분할됨.
    각 페이지에 필요한 항목만 로드. 메인 페이지를 렌더링 하면, 다른 페이지의 코드는 처음에 제공되지 않습니다. 덕분에 수백개의 페이지가 있어도 원하는 페이지 (ex. 메인 페이지) 로드를 빠르게 할 수 있음.
    요청한 페이지만 로드되므로 다른 페이지들과 분리됩니다. 따라서 특정 페이지에 오류가 발생해도 나머지 애플리케이션을 정상적으로 작동
  • 페이지 간에 공유되는 모든 코드는 추가 탐색에서 동일한 코드를 다시 다운로드하지 않도록 다른 번들로 분할됨.
  • 초기 페이지 로드 후 Next.js는 사용자가 탐색할 가능성이 있는 다른 페이지의 코드를 미리 로드 할 수 있음.
  • 'Next.js'의 프로덕션 빌드에서 <Link> 요소가 Brower ViewPort에 나타날 때마다, Next.js는 백그라운드에서 연결된 페이지 코드를 자동으로 prefetch. <Link> 를 클릭할 때 해당 링크와 연결된 페이지의 코드가 이미 백그라운드에 로드되어 있어, 페이지 전환이 즉시 이루어짐.
  • 동적 가져오기는 처음에 로드된 코드를 수동으로 분할하는 또 다른 방법.

Build Time & Run Time

Build Time (또는 빌드 단계)

프로덕션을 위해 애플리케이션 코드를 준비하는 일련의 단계에 지정된 이름.

애플리케이션을 빌드할 때 Next.js는 코드를 서버 에 배포 하고 사용자가 사용할 준비가 된 프로덕션 최적화 파일로 변환. 이러한 파일에는 다음이 포함됨.

  • 정적으로 생성된 페이지용 HTML 파일
  • 서버 에서 페이지를 렌더링하기 위한 JavaScript 코드
  • 클라이언트 에서 페이지를 대화형으로 만들기 위한 JavaScript 코드
  • CSS 파일
    런타임 (또는 요청 시간)은 애플리케이션 이 빌드 및 배포된 후 사용자 요청에 대한 응답으로 애플리케이션이 실행되는 기간을 나타냄.

Client & Server

Client

웹 응용 프로그램의 맥락에서 클라이언트는 응용 프로그램 코드에 대한 요청을 서버에 보내는 사용자 장치의 브라우저를 나타냄. 그런 다음 서버에서 받은 응답을 사용자가 상호 작용할 수 있는 인터페이스로 바꿈.

Server

애플리케이션 코드를 저장하고, 클라이언트로부터 요청을 받고, 일부 계산을 수행하고, 적절한 응답을 다시 보내는 데이터 센터의 컴퓨터를 말함.

Rendering

React에서 작성하는 코드를 UI의 HTML 표현으로 변환하는 프로세스

렌더링은 서버 또는 클라이언트에서 발생할 수 있음. 빌드 시 미리 또는 런타임 시 모든 요청에 대해 발생할 수 있음.

Next.js를 사용하면 서버 측 렌더링, 정적 사이트 생성 및 클라이언트 측 렌더링 세 가지 유형의 렌더링 방법을 사용할 수 있음.

사전 렌더링

서버 측 렌더링 및 정적 사이트 생성 은 결과가 클라이언트로 전송되기 전에 외부 데이터를 가져오고 React 구성 요소를 HTML로 변환하기 때문에 사전 렌더링 이라고도 함

클라이언트측 렌더링과 사전 렌더링

표준 React 애플리케이션에서 브라우저는 UI를 구성하기 위한 JavaScript 지침과 함께 서버에서 빈 HTML 셸을 받음. 초기 렌더링 작업은 사용자 장치에서 발생하므로 이를 클라이언트 측 렌더링 이라고 함.

반대로 Next.js는 기본적으로 모든 페이지를 사전 렌더링함. 사전 렌더링은 HTML이 사용자 장치의 JavaScript에 의해 모두 수행되는 대신 서버에서 미리 생성됨 의미.

실제로 이것은 완전히 클라이언트 측에서 렌더링된 앱의 경우 렌더링 작업이 수행되는 동안 사용자에게 빈 페이지가 표시됨을 의미. 사용자가 구성된 HTML을 볼 수 있는 사전 렌더링된 앱과 비교

사전 렌더링의 두 가지 유형

Next.js의 장점은 정적 사이트 생성, 서버측 렌더링 또는 클라이언트측 렌더링 등 페이지별로 사용 사례에 가장 적합한 렌더링 방법을 선택할 수 있음.

SampleComponent.js (Create React App 기반)
import React, { useState, useEffect } from 'react';

const SampleComponent = () => {
  const DUMMY_DATA = [
    {
      id: 1,
      title: 'first data'
    },
    {
      id: 2,
      title: 'second data'
    }
  ]

  const [sampleState, setSampleState] = useState([]);
  useEffect(() => {
    setSampleState(DUMMY_DATA);
  }, []);
  
  return (
    <>
      {sampleState.map((data) => <h1 key={data.id}>{data.title}</h1>)}
    </>  
  )
};

export default SampleComponent;

최초 렌더링 시 sampleState는 비어있는 배열의 형태를 가지며 그 이후 useEffect를 통해 값이 채워지게 됨. 그 다음 두 번째 렌더링을 통해 그 렌더링된 값이 보여지는 형태. 이 과정에서 화면에 깜빡임이 생길 수 있고, 이는 안좋은 사용자 경험을 제공하게 되는 것.

사용자 대시보드 페이지에 적합. 대시보드는 비공개의 사용자별 페이지이므로 SEO와 관련이 없으며 페이지를 미리 렌더링 할 필요가 없음. 데이터는 자주 업데이트되므로 요청 시 데이터를 가져와야 함.

SWR
클라이언트 측에서 Data를 가져오는 경우 이 방법을 적극 권장.
캐싱, 재검증, 포커스 추적, 일정 간격으로 다시 가져오기 등을 처리.

이러한 문제를 Next.js에서 제공하는 사전 렌더링 기능으로 해결할 수 있음. 렌더링 이전에 미리 값을 지정해두어 최초 렌더링 시 바로 값이 보이게끔 하는 것.

이러한 사전 렌더링(pre-rendering)을 위한 data fetching을 할 수 있는 기능으로 getStaticProps와 getServerSideProps가 있음. 두개가 제공하는 기능이 다르므로 어떤 상황에서는 어떤 것을 쓰는 게 더 좋을지 알아봄.

서버측 렌더링 ( SSR : Server Side Rendering )

서버 측 렌더링을 사용하면 페이지의 HTML이 각 요청에 대해 서버에서 생성. 그런 다음 생성된 HTML, JSON 데이터 및 페이지를 대화형으로 만들기 위한 JavaScript 지침이 클라이언트로 전송.

클라이언트에서 HTML은 빠른 비대화형 페이지를 표시하는 데 사용되는 반면 React는 JSON 데이터 및 JavaScript 명령을 사용하여 구성 요소를 대화형으로 만듦(예: 버튼에 이벤트 핸들러 첨부). 이 과정을 Hydrate : 브라우저가 모두 로드되었을 때 사용자와 상호작용 할 수 있는 상태 라고함.

Next.js에서 getServerSideProps를 사용하여 서버 측 렌더링 페이지를 선택할 수 있음.

참고: React 18 및 Next 12 에는 React 서버 구성 요소 의 알파 버전이 도입됨. 서버 구성 요소는 서버에서 완전히 렌더링되며 렌더링을 위해 클라이언트 측 JavaScript가 필요하지 않음. 또한 서버 구성 요소를 사용하면 개발자가 일부 논리를 서버에 유지하고 해당 논리의 결과만 클라이언트에 보낼 수 있음. 이렇게 하면 클라이언트로 전송되는 번들 크기가 줄어들고 클라이언트 측 렌더링 성능이 향상됨.

getServerSideProps

"page가 요청 받을 때마다" 호출 되어 pre-rendering
pre-render가 꼭 필요한 동적 데이터가 있는 page에 사용.
매 요청마다 호출되므로 성능은 getStaticProps에 뒤지지만, 내용을 언제든 동적으로 수정이 가능.

const DUMMY_DATA = [
  {
    id: 1,
    title: "first data",
  },
  {
    id: 2,
    title: "second data",
  },
];

const SampleComponent = (props) => {
  return (
    <>
      {props.data.map((data) => (
        <h1 key={data.id}>{data.title}</h1>
      ))}
    </>
  );
};

export async function getServerSideProps(context) {
  const req = context.req;
  const res = context.res;

  return {
    props: {
      data: DUMMY_DATA, // 페이지 컴포넌트에 props로 넘길 것
    },
  };
}

export default SampleComponent;

이 경우도 마찬가지로 잘 렌더링됨. 다만 서버쪽에서 렌더링하는 것이므로 파라미터로 context를 받아 서버의 요청 및 응답을 저장하는 req, res 변수를 저장해둬야 함.

따로 revalitdate로 주기를 정하지 않는 이상 다시 업데이트하지 않는 getStaticProps에 비해 요청이 들어올 때마다 호출하기 때문에 getServerSideProps는 getStaticProps에 비해 성능상으로는 안 좋지만 내용을 언제든지 수정할 수 있다는 특장점이 있음.

return
위 코드에는 props만 반환하고 있지만 getServerSideProps는
세 가지 반환 값을 가짐.

  • props : 컴포넌트로 리턴할 값.
  • redirect : 페이지 접속 시 지정한 경로로 리디렉션 시키기 위해 사용되는 값.
    { destination : string, permanent : boolean} 형태.
    status 코드가 필요하거나 변경해야 할 때는 permanent 대신 statusCode를 사용함.
  • notFound : boolean 값입니다. true일 경우 404 status와 에러 페이지를 보여줌.

context
context 객체를 인자로 받으며 아래 정보들을 담음.

params: 다이나믹 라우트 페이지면 해당 데이터를 가져옴.
req: Request 정보
res: Response 정보
query: 쿼리 스트링
preview: preview mode 사용 유무
previewData: preview mode 사용 시 전달된 데이터
resolvedUrl: 짧은 URL
ex) http://localhost:3000/detail/100 -> /detail/740
locale: 현재 locale 정보
locales: 지원되는 모든 locale 정보
defaultLocale: 기본 locale 정보

정적 사이트 생성 ( SSG : Static Site Generation )

정적 사이트 생성을 사용하면 HTML이 서버에서 생성되지만 서버측 렌더링과 달리 런타임에는 서버가 없음. 대신 콘텐츠는 빌드 시 애플리케이션이 배포될 때 한 번 생성되며 HTML은 CDN에 저장되고 각 요청에 대해 재 사용됨.

Next.js에서 getStaticProps를 사용하여 페이지를 정적으로 생성하도록 선택할 수 있음.

참고: 점진적 정적 재생성을 사용하여 사이트를 구축한 후 정적 페이지를 생성하거나 업데이트  할 수 있음. 즉, 데이터가 변경되더라도 전체 사이트를 재구축할 필요가 없음.
getStaticProps

"개발 환경에서는 npm run dev 시 페이지는 모든 요청에 대해 미리 렌더링", "프로덕션 환경에서는 빌드 시에 딱 한 번"만 호출되고, 바로 static file로 빌드 됨. 따라서, 이후 수정이 불가능.

앱 빌드 후에 웬만하면 바뀌지 않는 내용 (고정된 내용)이 있는 page가 있는 경우에만 사용.
데이터 유무에 관계 없이 수행 가능.
때로는 외부 데이터를 먼저 갖오지 않고는 HTML을 렌더링하지 못할 수 있음. 파일 시스템에 액세스하거나 외부 API를 가져오거나 빌드 시 데이터베이스를 쿼리해야 할 수 있음. Next.js는 이 경우( 데이터를 사용한 정적 생성 )를 지원.

Next.js에서는 페이지 구성 요소를 내보낼 때 이라는 async함수 도 내보낼 수 있음.

export default function Home(props) { ... }

export async function getStaticProps() {
  // Get external data from the file system, API, DB, etc.
  const data = ...

  // The value of the `props` key will be
  //  passed to the `Home` component
  return {
    props: ...
  }
}

getStaticProps프로덕션에서 빌드 시간에 실행 되고,
함수 내에서 외부 데이터를 가져와 페이지에 대한 소품으로 보낼 수 있음.

데이터베이스 직접 쿼리 가능.

import someDatabaseSDK from 'someDatabaseSDK'

const databaseClient = someDatabaseSDK.createClient(...)

export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from a database
  return databaseClient.query('SELECT posts...')
}

서버 측에서만 실행. 클라이언트 측에서는 실행 되지 않음. 브라우저용 JS 번들에도 포함되지 않음. 즉, 브라우저로 보내지 않고도 직접 데이터베이스 쿼리와 같은 코드를 작성할 수 있음. getStaticPaths fallback을 활용해서 성능 향상 가능.

장점은 호출 시 마다 매번 data fetch를 하지 않으니getServerSideProps보다 성능면에서 좋음.

const DUMMY_DATA = [
  {
    id: 1,
    title: "first data",
  },
  {
    id: 2,
    title: "second data",
  },
];

const SampleComponent = (props) => {
  return (
    <>
      {props.data.map((data) => (
        <h1 key={data.id}>{data.title}</h1>
      ))}
    </>
  );
};

export async function getStaticProps() {
  return {
    props: {
      data: DUMMY_DATA,
    },
    // revalidate: 10,
  };
}

export default SampleComponent;

위와 같은 static generation을 사용했을 때는 빈 배열을 지정하고 최초 렌더링 시 그 배열에 값을 저장하는 형태가 전혀 필요없음. 따라서 Hooks들을 전부 지워줘도 됨. 아래 사진처럼 잘 나옴.


물론 이 getStaticProps의 경우에도 요청이 들어올 때마다 주기적으로 업데이트할 수 있도록 return문 내부 파라미터로 revalidate를 넣을 수 있음. 위 코드블럭에서 주석처리한 부분을 적용한다면 10초마다 주기적으로 업데이트하게 됨.

후술할 getServerSideProps보다 호출 시 데이터 fetch를 하지 않으므로 성능면에서 더 좋음.

getStaticPaths

getStaticProps와 동일하게 빌드시에 서버에서 데이터를 받아오는데 사용.
getStaticPaths는 Dynamic Routes 할 때 사용.

// [id].js
const Detail = ({ data }) => {
  return <></>;
};

export async function getStaticPaths() {
  return {
    paths: [
      { params: { id: "100" } },
      { params: { id: "200" } },
      { params: { id: "300" } }
    ],
    fallback: true // false or 'blocking'
  };
}

export async function getStaticProps(context) {
  const data = await fetch("--");
  return {
    props: { data }, // 페이지 컴포넌트에 props로 넘길 것
  };
}

export default Detail;

getStaticProps와 반드시 같이 사용하며 getServerSideProps와는 같이 사용할 수 없음.
path로 설정된 경로들은 빌드시 미리 만들어짐.
위 예시는 id값이 100, 200, 300인 페이지를 미리 생성.

  • false : 404 전달.
  • true : 404 전달 하지 않고, "fallback" 버전 페이지 첫 request에서 보여준 후, 페이지가 생성되고 나면 그 이후의 request부터는 생성된 페이지를 보여줌.
  • blocking : 서버 사이드 렌더링을 통해 HTML이 생성되기 까지 기다림.

Network

애플리케이션 코드가 저장되고 네트워크에 배포된 후 실행되는 위치를 아는 것이 도움됨. 네트워크를 리소스를 공유할 수 있는 연결된 컴퓨터(또는 서버)로 생각할 수 있습니다. Next.js 애플리케이션의 경우 애플리케이션 코드를 원본 서버, 콘텐츠 전송 네트워크(CDN) 및 Edge 에 배포할 수 있음.

Origin Server

앞에서 설명한 것처럼 서버는 애플리케이션 코드의 원래 버전을 저장하고 실행하는 주 컴퓨터.

이 서버를 CDN 서버 및 에지 서버 와 같이 응용 프로그램 코드가 배포될 수 있는 다른 위치와 구별하기 위해 원본 이라는 용어 사용.

원본 서버가 요청을 받으면 응답을 보내기 전에 일부 계산을 수행. 이 계산 작업의 결과는 CDN(Content Delivery Network)으로 이동할 수 있음.

CDN ( Content Delivery Network )

전 세계 여러 위치에 정적 콘텐츠(예: HTML 및 이미지 파일)를 저장하고 클라이언트와 원본 서버 사이에 배치. 새로운 요청이 들어오면 사용자에게 가장 가까운 CDN 위치가 캐시된 결과로 응답할 수 있음.

계산이 각 요청에서 발생할 필요가 없기 때문에 이렇게 하면 Origin 부하가 줄어듦. 또한 사용자에게 지리적으로 더 가까운 위치에서 응답이 오기 때문에 사용자가 더 빠르게 작업할 수 있음.

Next.js에서는 사전 렌더링을 미리 수행할 수 있으므로 CDN은 작업의 정적 결과를 저장하는 데 적합하므로 콘텐츠 전달 속도가 빨라짐.

The Edge

Edge는 사용자에게 가장 가까운 네트워크 주변부(또는 에지)에 대한 일반화된 개념. CDN은 네트워크 주변부(에지)에 정적 콘텐츠를 저장하기 때문에 "에지"의 일부로 간주될 수 있음.

CDN과 유사하게 에지 서버는 전 세계 여러 위치에 배포됨. 그러나 정적 콘텐츠를 저장하는 CDN과 달리 일부 에지 서버는 작은 코드 조각을 실행할 수 있음.

이는 캐싱 과 코드 실행 모두 사용자에게 더 가까운 에지에서 수행할 수 있음을 의미.

전통적으로 클라이언트 측 또는 서버 측에서 수행되었던 일부 작업을 Edge로 이동하면 클라이언트로 전송되는 코드의 양이 줄어 들고 사용자 요청 일부가 모두 처리될 필요가 없기 때문에 애플리케이션의 성능을 높일 수 있음. 원본 서버로 돌아가는 방법 - 따라서 대기 시간이 줄어듦. 여기에서 Next.js를 사용한 Edge 예제를 참조.

Next.js에서는 Edge에서 미들웨어 로 코드를 실행할 수 있으며 곧 React Server Components 로 코드를 실행할 수 있음.

Optimizing Fonts( 글꼴 최적화 )

next/font
모든 글꼴 파일 에 대한 자동 자체 호스팅이 내장되어 있음. 즉, 사용된 기본 CSS 속성 덕분에 레이아웃 이동 없이 웹 글꼴을 최적으로 로드할 수 있음.
성능 향상과 개인 정보 보호를 염두에 두고 모든 Google 글꼴(맞춤형 글꼴 포함)을 최적화하고, 편리하게 사용할 수 있음. 외부 네트워크 요청을 제거함.
CSS 및 글꼴 파일은 빌드 시 다운로드되며 나머지 정적 자산과 함께 자체 호스팅 됨. 브라우저에서 Google로 요청을 보내지 않음.

Google Fonts

모든 Google 글꼴을 자동으로 자체 호스팅 함. 글꼴은 배포에 포함되며 배포와 동일한 도메인에서 제공됨. 브라우저에서 Google로 요청을 보내지 않음.

next/font/google 시작하려면 함수 에서 사용하려는 글꼴을 가져옴. 최상의 성능과 유연성을 위해 가변 글꼴을 사용하는 것이 좋음.

모든 페이지에서 글꼴을 사용하려면 아래와 같이 /pages 아래의 _app.js 파일에 글꼴을 추가.

// pages/_app.js
import { Inter } from 'next/font/google'

// If loading a variable font, you don't need to specify the font weight
const inter = Inter({ subsets: ['latin'] })

export default function MyApp({ Component, pageProps }) {
  return (
    <main className={inter.className}>
      <Component {...pageProps} />
    </main>
  )
}

가변 글꼴을 사용할 수 없는 경우 두께를 지정.

// pages/_app.js
import { Roboto } from 'next/font/google'

const roboto = Roboto({
  weight: '400',
  subsets: ['latin'],
})

export default function MyApp({ Component, pageProps }) {
  return (
    <main className={roboto.className}>
      <Component {...pageProps} />
    </main>
  )
}

배열을 사용하여 여러 가중치 및/또는 스타일을 지정할 수 있음.

const roboto = Roboto({
  weight: ['400', '700'],
  style: ['normal', 'italic'],
  subsets: ['latin'],
})

이름에 공백이 있는 글꼴에 사용할 수 있음.
Ex) Titillium Web를 Titillium_Web.

<head>에서 글꼴 적용

className 다음과 같이 래퍼 없이 글꼴 을 내부에 주입하여 글꼴을 사용할 수도 있음.

// pages/_app.js
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export default function MyApp({ Component, pageProps }) {
  return (
    <>
      <style jsx global>{`
        html {
          font-family: ${inter.style.fontFamily};
        }
      `}</style>
      <Component {...pageProps} />
    </>
  )
}
단일 페이지 사용

단일 페이지에서 글꼴을 사용하려면 아래와 같이 특정 페이지에 글꼴을 추가

// pages/index.js
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export default function Home() {
  return (
    <div className={inter.className}>
      <p>Hello World</p>
    </div>
  )
}
하위 집합 지정

Google 글꼴은 자동으로 하위 집합. 이렇게 하면 글꼴 파일의 크기가 줄어들고 성능이 향상됨. 미리 로드할 하위 집합을 정의해야 함. preload가 true 인 동안 하위 집합을 지정하지 않으면 경고가 발생함.
이는 두 가지 방법으로 수행할 수 있음.

  • 함수 호출에 추가하여 글꼴별로 글꼴 단위로
// pages/_app.js
const inter = Inter({ subsets: ['latin'] })
  • 귀하의 모든 글꼴에 대해 전역적으로next.config.js
// next.config.js
module.exports = {
  experimental: {
    fontLoaders: [
      { loader: 'next/font/google', options: { subsets: ['latin'] } },
    ],
  },
}

둘 다 구성된 경우 함수 호출의 하위 집합이 사용됨.

Local Fonts

next/font/local을 가져오고 로컬 글꼴 파일의 src를 지정함.
최상의 성능과 유연성을 위해 가변 글꼴을 사용하는 것이 좋음.

// pages/_app.js
import localFont from 'next/font/local'

// Font files can be colocated inside of `pages`
const myFont = localFont({ src: './my-font.woff2' })

export default function MyApp({ Component, pageProps }) {
  return (
    <main className={myFont.className}>
      <Component {...pageProps} />
    </main>
  )
}

단일 글꼴 패밀리에 대해 여러 파일을 사용하려는 경우 src배열이 될 수 있음.

const roboto = localFont({
  src: [
    {
      path: './Roboto-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: './Roboto-Italic.woff2',
      weight: '400',
      style: 'italic',
    },
    {
      path: './Roboto-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
    {
      path: './Roboto-BoldItalic.woff2',
      weight: '700',
      style: 'italic',
    },
  ],
})

Dynamic Routes

  • 미리 정의된 URL 주소로만 라우팅하는 것이 아니라 사용자가 접근한 경로 혹은 상황에 따라 동적인 라우팅을 제공하고 싶을 때 사용할 수 있는 방식.
  • 이를테면, /my-profile/ 뒤에 이름을 주어서 회원들의 프로필을 표현하고 싶다면? /my-profile/ray-kim 페이지에서는 ray-kim 의 프로필을 제공하고, /my-profile/jake-seo 페이지에서는 jake-seo 의 프로필을 제공하고 싶을 때 사용할 수 있음.
  • Next.js 에서는 [param] 과 같이 페이지에 존재하는 컴포넌트 파일명에 괄호를 씌우는 것으로 가능. 이를테면 pages/my-profile/[name].js 와 같이 경로를 구성하면 위에서 예로 들었던 /my-profile/jake-seo 와 같은 URL이 잘 매칭되어 라우팅됨.

동적 라우팅 예제

블로그에 post 라는 페이지가 존재하고 각각의 post 는 pid 라는 일련 번호로 구분한다고 가정했을 때 아래와 같은 코드가 나올 수 있음.

import { useRouter } from 'next/router'

const Post = () => {
  const router = useRouter()
  const { pid } = router.query

  return <p>Post: {pid}</p>
}

export default Post
  • 위의 소스코드에서 pid 값을 router.query 오브젝트를 통해 가져오고 있는데, router.query 오브젝트 내부의 pid 프로퍼티 값은 경로에 의해 설정됨.
  • /post/123 경로에 접근했을 때, router.query 내부의 pid 프로퍼티 값은 123 이 됨.
{ "pid": "123" }
  • 위와 같은 오브젝트가 router.query 에 들어있다고 생각하면됨.
  • post/123?foo=bar 경로에 접근한다면, router.query 내부의 값은 다음과 같을 것.
{"pid": "123", "foo": "bar"}

다중 동적 라우팅

  • 동적 라우팅은 경로 중 하나만 동적으로 설정 가능한 것이 아니라, pages/post/[pid]/[comment].js 와 같이 디렉토리 이름에 [] 를 붙여 중간 경로를 동적으로 만들 수도 있음.
    • post/abc/a-comment 와 같은 경로로 접속한다면,
      router.query 에는 아래와 같은 값이 들어감.
{
  "pid": "abc",
  "comment": "a-comment"
}
  • 다중 동적 라우팅도 next.js 의 컴포넌트인 next/link 로 가능.
import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link href="/post/abc">
          <a>Go to pages/post/[pid].js</a>
        </Link>
      </li>
      <li>
        <Link href="/post/abc?foo=bar">
          <a>Also goes to pages/post/[pid].js</a>
        </Link>
      </li>
      <li>
        <Link href="/post/abc/a-comment">
          <a>Go to pages/post/[pid]/[comment].js</a>
        </Link>
      </li>
    </ul>
  )
}

export default Home

가변 동적 (catch all) 라우팅 구성하기

  • 때에 따라 크기가 달라지는 배열을 받고 싶다면, pages/post/[...slug].js 와 같이 경로를 구성하면 됨. slug 는 [...param] 과 같이 원하는 이름으로 바꾸어도 됨.
  • pages/post/a/b/c 라는 경로로 접근하면 router.query 에 다음과 같은 값이 들어갈 것.
{
  "slug": ["a", "b", "c"]
}

만일, 파일 이름을 [...param] 으로 지었다면, router.query 에는 { "param": ["a", "b", "c"] } 값이 들어있었을 것

옵셔널 가변 (optional catch all) 동적 라우팅 구성하기

옵셔널 가변 동적 라우팅이란 겉보기에 용어는 굉장히 어렵지만 간단히 논리로 표현하자면, 0개 ~ n개까지 들어올 수 있는 경우를 말함.

{ } // GET `/post` (empty object)
{ "slug": ["a"] } // `GET /post/a` (single-element array)
{ "slug": ["a", "b"] } // `GET /post/a/b` (multi-element array)
  • 가변 동적 라우팅에서 empty object 케이스가 추가됨.
  • 적용하는 방법은 [[...slug]] 와 같이 가변 동적 라우팅을 적용하는 방법에서 [] 를 한번 더 덧씌우면 됨.

동적 라우팅 쿼리 파라미터를 가져올 때 주의사항

동적 라우팅 URL 에서 받은 쿼리 파라미터를 이용할 때, 보통 useRouter() 를 이용하여 router 오브젝트를 가져와서 router.query 와 같은 방식으로 꺼내게 됨.

export default function MovieDetail() {
  const router = useRouter();
  const [title, id] = router.query.params
  return <div>{title}</div>;
}

위와 같이 코딩한 뒤에 SPA의 메인 페이지부터 페이지를 타고타고 넘어오지 않고 바로 해당 URL 을 입력하여 접속하면 에러가 나게 됨. 그 이유는 router 가 아직 초기화되지 않았기 때문. router 가 초기화되지 않았고 그 상태에서 const [title, id] 를 이용해 구조분해 할당을 하려고 하니 undefined is not iterable 에러가 남.

next.js 는 해당 내용이 들어있는 HTML 자체는 먼저 만들어놓지만, 그렇다고 해서 그 HTML 이 사용자에게 정상적으로 보이는 상태라는 뜻은 아님. 일단 정적인 정보만 만들어놓고 이후에 hydrate 가 일어나면, 그제서야 정말 동작하는 페이지가 됨. 메인페이지부터 접근했을 때는 동작을 담당하는 main.js 가 이미 다운로드 완료된 상태에서 Client-side transitions 가 일어나기 때문에 큰 문제가 없지만, 특정 페이지에서부터 시작하는 경우 정적 HTML 일부는 만들어져있지만, hydrate 에 필요한 main.js 도 다운로드 되어있지 않아 동작에 필요한 js 를 로드할 시간이 필요함.

그래서 이럴 때는 아래와 같이 코딩하면 에러가 나지 않음.

import { useRouter } from "next/router";

export default function MovieDetail() {
  const router = useRouter();
  const [title, id] = router.query.params || ["LOADING"];

  return <div>The Title is : {title}</div>;
}

router 가 존재하지 않을 때만, 임시로 쓸 값을 넣어주면 됨.

참고로 이러한 이유로 URL 에 직접 접근했을 때는 console.log(router.query) 가 두 번 표기된다.

주의 사항

  • 미리 정의된 고정된 라우팅이 동적 라우팅보다 우선순위 가짐.
    예시 1
    • pages/post/create.js 는 /post/create 와 매칭될 것.
    • pages/post/[pid].js 는 /post/1, /post/abc 와 매칭됨. 그러나 create.js 가 있다면, /post/create 와는 매칭되지 않음.
    • 미리 정의된 라우팅이 동적 라우팅보다 우선순위를 가짐.
  • 동적 라우팅이 가변 동적 라우팅보다 우선순위를 가짐.
    예시 2
    • pages/post/[...slug].js 는 /post/1/2, /post/a/b/c 와 매칭됨.
    • 그러나 /post/create, /post/abc 와 매칭되지 않음. 이는 각각 미리 정의된 동적 라우팅과 다중 동적 라우팅이 아닌 그냥 동적 라우팅에 이미 정의되어 있기 때문.
  • Automatic Static Optimization 에 의해 정적으로 최적화되는 페이지는 route 파라미터가 제공되지 않아도 hydrated 될 것.
  • hydration 이후에나 next.js 는 query 오브젝트 내부에 route 파라미터를 제공하기 위해 앱을 업데이트하도록 트리깅 됨.

Next.js 에서의 hydration 이란 단순히 HTML 이 만들어진 것이 아닌 js 소스코드를 정상적으로 불러와 상호작용이 가능한 상태를 말함.

<Link> 컴포넌트를 사용해 a 태그 감싸줌. 웹사이트가 크롤링되어 SEO에 유리. 페이지를 다시 로드 하지않고 SPA동작처럼 보이게 함.

import Link from 'next/link'

<h1 className="title">
  Read{' '}
  <Link href ="/posts/first-post">
    <a>this page!</a>
  </Link>
</h1>

Client-side navigation ( 클라이언트 측 탐색 )

페이지 전환이 javascript로 이루어지는 것. 브라우저 기본 navigation(탐색)보다 훨씬 빠르게 작동하며, 리액트 SPA(Single Page Application) 특성 유지하며 페이지 전환 할 수 있음.

확인 방법은 아래와 같음.

  • 브라우저의 개발자 도구를 사용하여 CSS background속성을 <html>yellow로 변경 .
  • 두 페이지 사이를 앞뒤로 이동하려면 링크 클릭.
  • 페이지 전환 사이에 노란색 배경이 지속되는 것을 볼 수 있음.
    이는 브라우저가 전체 페이지를 로드 하지 않고 클라이언트측 탐색이 작동하고 있음을 나타냄.


    a 태그 사용하면 브라우저 전체 새로 고침해서 링크 클릭 시 배경색지워짐.
    a 태그는 외부 페이지에 연결해야 하는 경우 사용.

Next/Router

Next.js에서 라우터를 사용하려면 useRouter 훅을 사용해서 router 객체에 접근할 수 있음.

import { useRouter } from 'next/router'

function ActiveLink({ children, href }) {
  const router = useRouter()
  const style = {
    marginRight: 10,
    color: router.asPath === href ? 'red' : 'black',
  }

  const handleClick = (e) => {
    e.preventDefault()
    router.push(href)
  }

  return (
    <a href={href} onClick={handleClick} style={style}>
      {children}
    </a>
  )
}

export default ActiveLink

라우터에서 자주 사용하는 메서드

router Object

router에서 반환된 객체 정의

  • pathname(String): /pages 뒤에 오는 현재 경로 파일의 경로.
    따라서, basePath및 locale후행 슬래시( trailingSlash: true )는 포함되지 않음.
  • query(동적 경로): Object 매개변수를 포함하여 개체로 구문 분석된 쿼리 문자열. 페이지가 서버 측 렌더링을 사용하지 않는 경우 사전 렌더링 중에 빈 객체가 됨. 기본값은{}
  • asPath(String): 검색 매개변수를 포함하고 trailingSlash구성을 존중하는 브라우저에 표시되는 경로. basePath및 locale포함되지 않음.
  • isFallback(boolean): 현재 페이지가 폴백 모드 인지 여부.
  • basePath(String): 활성 basePath (활성화된 경우).
  • locale(String): 활성 로케일(활성화된 경우).
  • locales(String[]): 지원되는 모든 로케일(활성화된 경우).
  • defaultLocale(String): 현재 기본 로케일(활성화된 경우).
  • domainLocales(Array<{domain, defaultLocale, locales}>): 구성된 모든 도메인 로케일.
  • isReady(boolean): 라우터 필드가 클라이언트 측에서 업데이트되고 사용할 준비가 되었는지 여부. useEffect메서드 내에서만 사용해야 하며 서버에서 조건부 렌더링에 사용해서는 안 됨. 자동으로 정적으로 최적화된 페이지 의 사용 사례는 관련 문서를 참조.
  • isPreview(boolean): 애플리케이션이 현재 미리보기 모드 인지 여부.

페이지가 서버 측 렌더링 또는 자동 정적 최적화를 사용하여 렌더링되는 경우 asPath필드를 사용하면 클라이언트와 서버 간에 불일치가 발생할 수 있음. isReady필드가 true가 될 때까지 asPath사용하면 안됨.

router.push

client-side 전환을 할 수 있도록 도와주고 Next/link 대신 사용할 수 있음.

router.push는 라우터 히스토리 스택에 새로운 url을 쌓아줌. 예를 들어 home > login > item 순으로 페이지를 이동했을 때, router.push를 사용해 'mypage'로 이동한다면 라우터 히스토리 스택에는 home > login > item > mypage가 쌓임. 마지막 페이지에서 뒤로가기를 누르면 'item' 페이지로 되돌아감.

router.push()는 window.location과 비슷하게 동작. <a>태그를 만들지 않기 때문에 크롤링되지 않아서 SEO에 불리함. 대부분 onClick과 같은 이벤트 핸들러와 같이 사용.

router.push(url, as, options)
  • url: [필수] 라우팅 하려는 url
  • as: [선택] 브라우저 url 바에 보여지는 path
  • options: [선택] ]scroll( default: true, 라우팅 후 스크롤업), shallow( default: false, getStaticProps, getServerSideProps, getInitialProps 다시 실행하지 않고 현재 페이지 경로 업데이트), locale(새로운 페이지의 locale) 등의 옵션이 있음.
    주의: router.push는 외부 url 사용시에는 적합하지 않음. a tag의 target="_blank" 를 사용하거나 window.location을 사용하는 것이 나음.

첫 번째 사용 방법

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.push('/about')}>
      Click me
    </button>
  )
}

두 번째 사용 방법
router.push(URL) 형식으로 해당 URL로 이동. 또한 버튼 클릭시 "/" 경로로 이동하며, query로 url에 정보를 담아줌.

import { useRouter } from "next/router"

const PushPage = () => {
    const router = useRouter();
    
    return (
    	<div>
            <button onClick={() => router.push({
                pathname: "/",
                query: {
                    name: "john",
                    age: "19",
                },
            })} value="Push">
                버튼
            </button>
        </div>
    )
}

export default PushPage;

router.replace

router.push와 비슷하게 동작하지만, 라우터 히스토리 스택에 새로운 url을 추가하지 않습니다. 대신 기존에 있던 현재 페이지 route를 새로운 url로 대체합니다.

예를 들어, home > login > item 순으로 페이지를 이동했을 때, router.replace를 사용해 'mypage'로 이동한다면 라우터 히스토리 스택에는 현재 페이지인 item이 mypage로 대체됩니다. 즉, home > login > mypage가 쌓입니다. 마지막 페이지에서 뒤로가기를 누르면 'login' 페이지로 되돌아갑니다.

router.replace(url, as, options)

첫 번째 방법

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.replace('/home')}>
      Click me
    </button>
  )
}

두 번째 방법
router.replace(URL1, URL2) 형식으로 URL1으로 이동후, URL2로 주소만 변경

import { useRouter } from "next/router"

const ReplacePage = () => {
    const router = useRouter();
    
    return (
    	<div>
            <button onClick={() => router.replace("/", "/replace")} value="Push">
                버튼
            </button>
        </div>
    )
}

export default ReplacePage;
  • router.push vs router.replace
    router.push는 라우터의 history 스택 제일 위에 새로운 url을 쌓는 것, router.replace는 스택 제일 위에 있는 원소를 새로운 url로 바꾸는 것.

이전 라우팅 히스토리 모두 유지하려면 router.push 사용.
현재 라우팅 히스토리를 다른 url로 변경하고 싶다면 (예: 로그인 후 마이페이지 이동했을 때. back 버튼 누르면 다시 로그인 페이지로 가지 않기 위해 로그인 url을 history에서 제거) router.replace 사용.

router.back

히스토리에서 전단계로 이동. 브라우저의 'back'버튼을 누르는 것과 동일하게 동작.

window.history.back()와 같이 동작.

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.back()}>
      Click here to go back
    </button>
  )
}

router.prefetch

빠른 클라이언트 전환을 위해 페이지를 데이터를 미리 가져옴.
next/link 의 경우 자동으로 페이지를 미리 가져오기 때문에 next/link 가 없는 탐색에서 유용

router.prefetch(url, as)
  • url
    이동할 url > url 객체 사용 가능
  • as
    이동 후 브라우저에 표시될 URL

router.beforePopState

경우에 따라(예를 들어 Custom Server를 사용하는 경우) popstate를 수신 하고 라우터가 작동하기 전에 작업을 수행할 수 있음.

router.beforePopState(cb)
  • cb : 들어오는 popstate 이벤트(사용자의 세션 기록 탐색으로 인해 현재 활성화된 기록 항목이 바뀔 때 발생)에서 실행되는 기능.
    • url : 새로운 상태에 대한 경로. 일반적으로 페이지의 이름.
    • as : 브라우저에 표시될 URL.
    • options : router.push에서 보낸 추가 옵션.
      cb가 false를 반환하는 경우 Next.js 라우터는 popstate를 처리하지 않으며 이 경우 처리에 대한 책임은 사용자에게 있음.( 파일 시스템 라우팅 비활성화 )

다음 예와 같이 beforePopState를 사용하여 요청을 조작하거나 SSR 새로 고침을 강제 할 수 있음.

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
      // I only want to allow these two routes!
      if (as !== '/' && as !== '/other') {
        // Have SSR render bad routes as a 404.
        window.location.href = as
        return false
      }

      return true
    })
  }, [])

  return <p>Welcome to the page</p>
}

router.reload

새로고침 버튼 클릭과 같음

router.events

next/router로 이벤트를 감지해서 특정 이벤트가 발생하면 함수를 실행

  • routeChangeStart
    경로가 변경되기 시작할때 발생
routeChangeStart(url, { shallow })  
  • routeChangeComplete
    경로가 완전히 변경되면 발생
routeChangeComplete(url, { shallow })  
  • routeChangeError
    경로 변경시 오류가 발생하거나 경로 전환 취소시 발생 (err.cancelled - 탐색이 취소되었는지 여부)
routeChangeError(url, { shallow })  
  • beforeHistoryChange
    브라우저의 history를 변경하기 전에 발생
beforeHistoryChange(url, { shallow })  
  • hashChangeStart
    해시는 변경되지만 페이지는 변경되지 않을때 발생
hashChangeStart(url, { shallow })  
  • hashChangeComplete
    해시가 변경되었지만 페이지는 변경되지 않을때 발생
hashChangeComplete(url, { shallow })  

Catch All Routes

페이지에서 모든 path를 가져와 쿼리파라미터화 하는 방법이다. 이를 활용하면 URL에 원하는 데이터를 출력하는 것은 물론 그 데이터를 활용할 수 있게 되어 유용하다.

pages폴더에 영화에 대한 정보를 출력해주는 페이지가 있다고 예시로 들어봄.
폴더에 pages/movies/[...params].js라는 경로의 파일을 생성하면, 브라우저에서 /movies이후에 몇 개의 path가 붙든 [...params].js 파일로 연결됨. 또한 /movies 이후에 오는 path들은 모두 파라미터화가 되어 useRouterquery객체 안에 params 배열에서 확인이 가능.

다시 말해, 만약 경로가 /movies/movies/Spider-Man:%20No%20Way%20Home/634649이라면, useRouter query에는

{
  params: ["Spider-Man: No Way Home", "634649"];
}

가 존재하게 됨. 이를 활용한다면,

export default function MovieDetail() {
  const router = useRouter();
  const [title, id] = router.query.params || [];
  return (
    <div>
      <h4>ID: {id}</h4>
      <h4>Movie Name:{title}</h4>
    </div>
  );
}

처럼 코드를 작성하여 화면에 영화의 아이디와 이름을 출력할 수 있는 것.

WithRouter

라우터에 의해서 호출된 컴포넌트가 아니어도 match, location, history 객체에 접근할 수 있도록 해줌.

import { withRouter } from 'next/router'

function Page({ router }) {
  return <p>{router.pathname}</p>
}

export default withRouter(Page)

TypeScript

withRouter와 함께 클래스 구성 요소를 사용하려면 Component가 router prop을 수락해야 함.

Static File

Next.js는 루트 디렉터리에 있는 public폴더 아래에 이미지와 같은 정적 파일을 제공할 수 있음. public 그러면 기본 URL( )에서 시작하는 코드에서 내부 파일을 참조할 수 있음 /.
예를 들어 에 이미지를 추가하면 public/me.png다음 코드가 이미지에 액세스.

import Image from 'next/image'

function Avatar() {
  return <Image src="/me.png" alt="me" width="64" height="64" />
}

export default Avatar

Image

최적화되지 않은 이미지
<img src="/images/profile.jpg" alt="Your Name" />

그러나 이는 다음을 수동으로 처리해야 함을 의미.

다양한 화면 크기에서 이미지가 반응하는지 확인.
타사 도구 또는 라이브러리로 이미지 최적화.
뷰포트에 들어갈 때만 이미지를 로드.
그리고 더. 대신 Next.js는 Image이를 처리하기 위해 즉시 사용 가능한 구성 요소를 제공함.

이미지 구성 요소 및 이미지 최적화

next/image : 현대 웹을 위해 진화된 HTML <img>요소 확장.

Next.js는 기본적으로 이미지 최적화도 지원. 이를 통해 브라우저에서 지원하는 경우 WebP 와 같은 최신 형식으로 이미지 크기 조정, 최적화 및 제공이 가능. 이렇게 하면 뷰포트가 더 작은 장치에 큰 이미지가 전달되는 것을 방지할 수 있음. 또한 Next.js가 향후 이미지 형식을 자동으로 채택하고 해당 형식을 지원하는 브라우저에 제공할 수 있음.

자동 이미지 최적화는 모든 이미지 소스에서 작동. 이미지가 CMS와 같은 외부 데이터 소스에서 호스팅되는 경우에도 여전히 최적화할 수 있음.

빌드 시 이미지를 최적화하는 대신 Next.js는 사용자가 요청할 때 주문형 이미지를 최적화 함. 정적 사이트 생성기 및 정적 전용 솔루션과 달리 10개의 이미지를 배송하든 1천만 개의 이미지를 배송하든 빌드 시간이 늘어나지 않음.

이미지는 기본적으로 지연 로드. 즉, 표시 영역 외부의 이미지에 대해 페이지 속도가 저하되지 않음. 이미지가 뷰포트로 스크롤되면서 로드.

이미지는 항상 Google이 검색 순위에 사용할 Core Web Vital인 Cumulative Layout Shift를 피하는 방식으로 렌더링.

자동 이미지 최적화 문서
next/Image 문서

Built-In CSS ( 내장 CSS 지원 )

Next.js를 사용하면 JavaScript 파일에서 CSS 파일을 import해서 가져올 수 있음. 이는 Next.js가 자바스크립트를 넘어서는 개념을 확장했기 때문에 가능함.

Adding a Global Stylesheet

애플리케이션에 스타일시트를 추가하려면 pages/_app.js.

예를 들어 다음과 같은 스타일시트를 고려styles.css.

body {
  font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica',
    'Arial', sans-serif;
  padding: 20px 20px 60px;
  max-width: 680px;
  margin: 0 auto;
}

아직 없는 경우 pages/_app.js파일을 만듦. 그런 다음 import파일 styles.css.

import '../styles.css'

// This default export is required in a new `pages/_app.js` file.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

이러한 스타일(styles.css)은 애플리케이션의 모든 페이지와 구성 요소에 적용됨. 스타일시트의 글로벌 특성으로 인해 충돌을 피하기 위해 pages/_app.js 내부에서만 가져올수 있음.

개발 단계에서 이러한 방식으로 Stylesheet를 표현하면 스타일을 편집할 때 핫 리로드할 수 있음. 즉, 애플리케이션 상태를 유지할 수 있음.

프로덕션에서 모든 CSS 파일은 자동으로 하나의 축소된 .css 파일로 연결됨.

Next.js CSS 문서

Adding Component-Level CSS

Next.js는 파일 명명 규칙을 사용하여 CSS 모듈을[name].module.css 지원.

CSS 모듈은 고유한 클래스 이름을 자동으로 생성하여 CSS 범위를 로컬로 지정. 이를 통해 충돌에 대한 걱정 없이 다른 파일에서 동일한 CSS 클래스 이름을 사용할 수 있음.

이 동작은 CSS 모듈을 Component-Level CSS를 포함하는 이상적인 방법으로 만듦. CSS Module File은 애플리케이션 어느 곳에서나 가져올 수 있음.

예를 들어 폴더 Button에 있는 재사용 가능한 구성 요소를 고려 components/.

먼저 components/Button.module.css다음 내용으로 생성.

/*
You do not need to worry about .error {} colliding with any other `.css` or
`.module.css` files!
*/
.error {
  color: white;
  background-color: red;
}

그런 다음 위의 CSS 파일을 만들고 components/Button.js가져오고 사용.

import styles from './Button.module.css'

export function Button() {
  return (
    <button
      type="button"
      // Note how the "error" class is accessed as a property on the imported
      // `styles` object.
      className={styles.error}
    >
      Destroy
    </button>
  )
}

CSS Module은 선택적 기능 이며 확장자 .module.css가 일반 <link>스타일시트와 전역 CSS File은 계속 지원.

프로덕션에서 모든 CSS Moudle File은 자동으로 많은 축소 및 코드 분할 .css 파일로 연결. 이러한 .css파일은 Application 핫 실행 경로를 나타내므로 Application이 페인트할 수 있도록 최소한의 CSS가 로드 되도록 함.

Polishing Layout

styles/utils.module.css

styles/utils.module.css다음 콘텐츠로 호출되는 새 CSS 파일을 추가.
이런 유틸리티 클래스는 애플리케이션 전체에서 재 사용 가능.

.heading2Xl {
  font-size: 2.5rem;
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: -0.05rem;
  margin: 1rem 0;
}

.headingXl {
  font-size: 2rem;
  line-height: 1.3;
  font-weight: 800;
  letter-spacing: -0.05rem;
  margin: 1rem 0;
}

.headingLg {
  font-size: 1.5rem;
  line-height: 1.4;
  margin: 1rem 0;
}

.headingMd {
  font-size: 1.2rem;
  line-height: 1.5;
}

.borderCircle {
  border-radius: 9999px;
}

.colorInherit {
  color: inherit;
}

.padding1px {
  padding-top: 1px;
}

.list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.listItem {
  margin: 0 0 1.25rem;
}

.lightText {
  color: #666;
}

Styling Tip

clsx 클래스 전환

클래스 이름을 쉽게 전환할 수 있는 간단한 라이브러리
npm install clsx 또는 yarn add clsx를 사용하여 설치할 수 있음.

import styles from './alert.module.css';
import { clsx } from 'clsx';

export default function Alert({ children, type }) {
  return (
    <div
      className={clsx({
        [styles.success]: type === 'success',
        [styles.error]: type === 'error',
      })}
    >
      {children}
    </div>
  );
}
.success {
  color: green;
}
.error {
  color: red;
}

clsx DOC

postCSS 구성 사용자 지정

구성 없이 바로 사용할 수 있는 Next.js는 PostCSS를 사용하여 CSS를 컴파일.

PostCSS 구성을 사용자 지정하려면 postcss.config.js. 이는 Tailwind CSS 와 같은 라이브러리를 사용하는 경우에 유용.

다음은 Tailwind CSS를 추가하는 단계

npm install -D tailwindcss autoprefixer postcss

그 다음 posscss.config.js 생성

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

또한 tailwind.config.js 옵션을 지정해 콘텐츠 소스를 구성하는 것이 좋음.

// tailwind.config.js
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    // For the best performance and to avoid false positives,
    // be as specific as possible with your content configuration.
  ],
};

PostCSS DOC

Sass 사용

npm install -D sass

TypeScript

기존 프로젝트에 tsconfig.json 생성

시작하려면 프로젝트의 루트에 tsconfig.json 빈 파일을 만듦.

touch tsconfig.json

실행 중이 아니면 npm run dev로 개발 서버 시작( 또는 yarn dev ). Next.js는 TypeScript 사용을 자동으로 감지하고 다음 메시지와 함께 필요한 패키지 설치.

  • Next.js는 tsconfig.json 파일을 채움.
  • next-env.d.ts ( TypeScript 컴파일러가 Next.js 유형을 선택하도록 함. 이 파일은 건들면 안 됨. ) 생성.

정적 생성 및 서버 측 렌더링

import { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next';

export const getStaticProps: GetStaticProps = async (context) => {
  // ...
};

export const getStaticPaths: GetStaticPaths = async () => {
  // ...
};

export const getServerSideProps: GetServerSideProps = async (context) => {
  // ...
};

API 경로

import { NextApiRequest, NextApiResponse } from 'next';

export default (req: NextApiRequest, res: NextApiResponse) => {
  // ...
};

_app.js

import { AppProps } from 'next/app';

function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default App;

SSR

완전한 html파일을 받아와 페이지 전체를 렌더링 하는 방식 ( Server에서 화면을 그려서 보내줌. Data fetching도 이뤄짐. HTML안에 이미 컨텐츠 들을 포함하고 있는 상태 )
클라이언트가 초기 화면을 로드하기 위해 Server에 요청을 보냄. Server는 화면을 표시 하는데 필요한 HTML, CSS, JS코드를 브라우저에 전달. HTML을 띄우고, 이어서 JS를 다운 받아 실행.

왜 사용하는가, 왜 생겼는가
1. 상위 노출이 필요
2. 페이지마다 데이터가 자주 바뀌는 경우

장점
1. 서버로부터 화면을 렌더링하기 위한 필수적인 요소를 먼저 가져와서 이후에 설명할 CSR보다 초기 로딩 속도가 빠름.
2. 서버로부터 받아오는 각 페이지에 검색엔진이 크롤링 할 때 필요한 정보가 들어있어 SEO에 유리
3. 사용자에 대한 정보를 서버측에서 Session(클라이언트에서 관리하는 쿠키보다 보안이 좋음)으로 관리

단점
1. 초기 로딩 속도가 빠른만큼 동시에 단점. TTV(Time To View)와 TTI(Time To Interact)간에 시간 간격이 존재하게 됨.
2. 매번 페이지를 요청할 때 마다 새로 고침(깜빡임)되서 사용자 경험이 떨어짐.
3. 페이지를 구성하는 모든 리소스를 준비해서 응답하므로 서버 부담이 증가됨.
4. 새로운 요청(클릭)이 생길 때 마다 바뀌지 않아도 되는 부분도 렌더링 됨.

CSR

필요한 부분만 응답 받아 브라우저에서 렌더링 하는 방식
클라이언트에서 초기 화면을 로드하기 위해 서버에 요청 보냄. 서버는 화면을 표시하는데 필요한 리소스로 응답함.
최초 불러온 html내용은 비어있음(html, body 태그만 존재). js 파일의 다운로드가 완료된 다음, 해당 js 파일이 dom을 빈 html 위에 그리기 시작. 초기 로딩 시간이 오래 걸림

왜 사용하는가, 왜 생겼는가
1. 보다 나은 사용자 경험 제공
2. 사용자와 상호작용이 많은 경우

장점
1. 빠른 속도 (초기 화면 렌더링 시x)
2. 서버 부하 감소 (필요한 부분만 요청/응답)
3. 사용자 친화적. 네이티브 앱과 유사한 사용자 경험(자연스러운 페이지 이동,깜박임x)

단점
1. SEO 불리(검색엔진 봇이 정적인 html페이지를 가지고 검색을 수행하기에, 정적으로 빈 html을 준 이후로 JS를 통해 동적으로 DOM을 그려주는 CSR방식은 SEO관점에서 불리할 수 밖에 없음. 검색엔진 봇들은 데이터를 크롤링 할 때, JavaScript파일을 해석할 수 없다는 특징이 있음. 때문에 HTML 파일에서 크롤링 하게 됨.) CSR에서 메타태그로만 SEO할 수 있음. (제한이 많을 수 밖에 없음.)
2. 초기 로딩 속도 느림(모든 js파일을 다운 받아와야 함)
3. Cookie말고는 사용자에 대한 정보를 저장할 공간이 마땅치 않음.

SEO

검색엔진이 이해하기 쉽도록 홈페이지를 개발해 검색 결과 상위에 노출될 수 있도록 하는 것

필요성
브라우저에 웹 페이지를 검색했을 때 잘 노출되어야 사용자 수, 판매량 증가 기대가능.

MPA(SSR)

여러 페이지로 구성된 웹 어플리케이션. 클릭과 같이 인터렉션이 발생할 때마다 서버로부터 새로운 html을 받아와서 페이지 전체를 새로 렌더링하는 전통적인 웹 페이지 구성 방식.

SPA(CSR)

단일 페이지로 구성된 웹 어플리케이션. 화면 이동시에 웹 페이지를 서버에서 다시 전달받지 않고, 필요한 데이터만 JSON으로 전달받아 동적으로 렌더링 함.

메타태그
웹 페이지가 담고 있는 컨텐츠가 아닌 웹 페이지 자체 정보 명시하기 위한 목적으로 사용되는 html태그. 웹 사이트 제목, 문서의 저자. 웹 페이지에 대한 설명 등의 내용을 담고 있음.

웹 사이트 vs 웹 어플리케이션

웹 사이트 : 정보 관람을 목적으로 정보를 제공하는 정적 사이트. 유저는 관람만 가능
Ex) 위키피디아
웹 어플리케이션 : 유저가 능동적으로 서비스를 이용하는 것 목적.
Ex) Goolge maps, Naver Map, KaKao Map ( 목적지까지 시간, 위치, 맛집, 영업 시간 등등 검색, 확대 축소, 리뷰 달기 )

공통점
화면이 HTML과 같은 마크업 언어로 표현됨.
Chrome, FireFox, Safari 등과 같은 웹 브라우저 상에서 구동
그래서 명칭에 '웹'이라는 단어가 들어감

결론
뉴스 사이트도 검색, 댓글 기능 등이 포함되서 둘을 구분하는 것이 모호해짐

모바일 웹

모바일 화면에 맞게 구성한 웹 ( 반응형 혹은 적응형 작업이 된 웹 페이지 )

웹 앱

모바일 웹과 비슷하지만 구동방식이 앱처럼 보이게한 앱
참고 : https://m.blog.naver.com/ithopenanum/221434223861

하이브리드 앱

웹 앱과 네이티브 앱의 기능을 결합해 개발된 앱
겉으로는 일반 어플로 보이지만 웹을 기반으로 함 ( 어플 안에 웹 페이지를 불러오는 방식. 실제로는 앱이 아닌 웹이 실행되는 것. )
Ex) 네이버, 다음, 크롬

네이티브 앱

모바일 기기에 최적화 된 네이티브 언어로 개발된 앱
Ex) 유튜브, 카카오톡, 인스타그램, 페이스북

참고
https://nextjs.org/learn/foundations/how-nextjs-works/rendering
https://beside-lab.tistory.com/entry/Nextjs-getStaticProps-vs-getServerSideProps-%EC%B0%A8%EC%9D%B4%EC%99%80-%ED%99%9C%EC%9A%A9
https://steadily-worked.tistory.com/849
https://talkwithcode.tistory.com/99
https://velog.io/@jwhan/nextjs-getStaticProps-getStaticPaths-getServerSideProps

profile
선명한 기억보다 흐릿한 메모

0개의 댓글