Next.js 기본 개념과 주요 기능들

0

왜 서버 사이드 렌더링이 필요할까요?

Next.js는 React 기반의 서버 사이드 렌더링 프레임워크입니다. 서버 사이드가 아닌 클라이언트 사이드 렌더링 방식에는 몇 가지 문제점이 있는데, 우선 로드해야 할 파일이 많은 경우 클라이언트 사이드는 모든 js 파일을 로드한 다음 웹 페이지를 그리기 때문에 사용자가 많은 시간을 기다려야 하는 일이 생길 수 있습니다.

또 클라이언트 사이드 렌더링은 SEO에 제대로 대응할 수 없는 문제도 있는데, 클라이언트 사이드 렌더링은 자바스크립트가 로드되지 않으면 아무런 정보를 표시하지 않기 때문입니다. 즉 구글의 검색엔진은 클라이언트 사이드 방식의 자바스크립트가 로드되지 않은 페이지에서는 아무런 정보도 수집할 수 없는 것이죠.

서버 사이드 렌더링은 이런 클라이언트 사이드 렌더링의 문제를 해결할 수 있는데, 서버에서 자바스크립트를 로딩하여 클라이언트 측에서 자바스크립트를 로딩하는 시간을 줄일 수 있고, 서버에서 직접 자바스크립트, html, css를 만들어 그려주기 때문에 검색엔진은 해당 페이지의 모든 정보를 수집할 수 있습니다. 또 SEO를 위한 meta 태그를 자유롭게 추가할 수도 있습니다.

Next.js의 주요 기능

hot reloading

Next.js로 개발하는 동안 저장하는 코드는 자동으로 새로고침이 되어 화면에 표시해 주기 때문에 편리합니다.

automatic routing

Next.js는 pages라는 폴더에 있는 파일을 기준으로, 해당 파일명을 도메인명으로 사용합니다.pages 폴더에 page1.tsx라는 파일을 추가하면 localhost:3000/page1와 같이 자동으로 라우팅 설정이 됩니다.

public 폴더도 pages 폴더와 동일하게 라우팅을 할 수 있지만, public 폴더는 모든 사람이 페이지에 접근할 수 있기 때문에 public 폴더를 이용하는 라우팅은 사용하지 않는 것이 좋습니다.

single file components

Next.js는 style jsx를 사용하여 컴포넌트 내부에 해당 컴포넌트만 스코프를 가지는 css를 만들수 있습니다. 또 다음과 같이 <style jsx global>을 사용하면 글로벌 스타일로 정의할 수도 있습니다.

styled-jsx 사용법
function Heading(props) {
  const variable = "red";
  return (
    <div className="title">
      <h1>{props.heading}</h1>
      <style jsx>
        {`
          h1 {
            color: ${variable};
          }
        `}
      </style>
    </div>
  );
}

export default function Home() {
  return (
    <div>
      // red
      <Heading heading="heading" />
      // block
      <h1>ttt</h1>
    </div>
  );
}

글로벌 스타일 정의

Next.js는 _app.tsx에만 글로벌 스타일을 정의할 수 있는데, 다른 컴포넌트에 정의한 경우에는 다른 클래스와 겹쳐 오류를 발생할 수 있기 때문입니다. 만약 다른 컴포넌트에 정의를 하게 되면 다음과 같은 오류가 발생됩니다.

컴포넌트 정의 에러
Global CSS cannot be imported from files other than your Custom <App>. Please move all global CSS imports to pages/_app.tsx. Or convert the import to Component-Level CSS (CSS Modules).
_app 정의 예제
// _app.tsx
import "./globals.css";

function MyApp({ Component, pageProps }) {
  return <Component ponent {...pageProps} />;
}

export default MyApp;

server rendering

Next.js는 서버 사이드 렌더링을 구현하는데, 클라이언트 사이드 렌더링과는 달리 서버 렌더링을 한 페이지의 소스보기를 클릭하면 내부에 소스가 있는 것을 알 수 있습니다.

code splitting

dynamic import를 이용하면 손쉽게 코드 스플리팅이 가능한데, 코드 스플리팅은 내가 원하는 페이지에서 원하는 자바스크립트와 라이브러리를 렌더링 하는 것을 뜻해. 즉 모든 번들이 하나로 묶이지 않고, 각각의 페이지로 나뉘기 때문에 자바스크립트 로딩 시간을 조금 더 효율적으로 개선할 수 있는 것이죠.

typescript

Next.js를 사용하면 타입스크립트 활용을 위해 웹팩이나 바벨을 설정할 필요가 없습니다. 그냥 yarn add typescript 명령어로 타입스크립트를 설치하고 yarn run dev 명령어를 입력하기만 하면 자동으로 tsconfig, next-end.d.ts 파일이 생성되어 타입스크립트를 사용할 수 있습니다.

_document.tsx

_document.tsx는 meta 태그를 정의하거나, 전체 페이지에 관여하는 컴포넌트로 render 요소는 반영하지만 페이지 구성 요소만 반영되고 js는 반영 하지 않기 때문에 console은 보이지 않습니다. 즉 _document.tsx에서는 componentDidMount 같은 훅도 실행 되지 않기 때문에 정말 static한 상황에만 사용하는 컴포넌트라고 할 수 있습니다.

pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from "next/document";
export default class CustomDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          // 모든페이지의 head에 아래 메타태그가 들어감
          // 루트파일이기 때문에 가능한 적은 코드만 넣어야함
          // 전역 파일을 엉망으로 만들면 안되며, 웹 타이틀, ga 같은 것 정도만 넣는 것이 좋음
          <meta property="custom" content="123123" />
        </Head>
        <body>
          <Main />
        </body>
        <NextScript />
      </Html>
    );
  }
}

_app.tsx

_app.tsx에서 렌더링 하는 값은 모든 페이지에 영향을 주게 됩니다. 이 컴포넌트는 Next.js에서 최초로 실행되는데, _app.tsx은 클라이언트에서 띄우길 바라는 전체 컴포넌트의 공통 레이아웃이기 때문에 최초 실행되면서 내부에 컴포넌트들을 실행하게 됩니다.

즉 내부에 컴포넌트가 있다면 전부 실행하고 htmlbody로 구성하는데, Component, pageProps를 받아서 처리합니다. 여기서 props로 받은 Component는 요청한 페이지이고, GET 요청을 보내면, Component에는 /pages/index.js 파일이 props로 내려오게 됩니다. pageProps는 페이지 getInitialProps를 통해 내려 받은 props들을 말합니다.

_app.tsx는 결국 페이지를 업데이트 하기 전에 원하는 방식으로 페이지를 업데이트 하는 통로 역할을 하는데, _app.tsx에서 console.log를 실행하면 clientserver 모두, 즉 localhost:3000 웹과 터미널에서 모두 콘솔을 확인할 수 있습니다.

_app.tsx
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default MyApp;

import style component와 sass

css 파일 import

Next.js는 다음과 같이 css 파일을 import해서 사용할 수 있습니다.

스타일 import하기
import styles from "./test.module.css";

function Heading(props) {
  // const variable = "red";
  return (
    <div className="title">
      <h1 className={styles.red}>{props.heading}</h1>
    </div>
  );
}

export default function Home() {
  return (
    <div>
      <Heading heading="heading" />
      <h1>스타일</h1>
    </div>
  );
}
test.module.css
h1.red {
  color: blue;
}

sass 사용하기

Next.js는 따로 config 파일을 정의 하지 않아도 css 파일을 scss로 바꾼 후 yarn add sass --dev를 입력하면 자동으로 설정이 됩니다.

Link와 동적 url

Link

일반적으로 페이지 간의 이동은 a 태그를 사용하지만, Next.js에서는 a 태그를 사용하면 처음 페이지에 진입시 번들 파일을 받고, a 태그에 의해 라우팅 되면 다시 번들 파일을 받기 때문에 사용하지 않습니다.redux를 쓰는 경우에는 storestate 값이 증발되는 현상도 일어나기 때문에 a 태그는 다른 사이트로 페이지를 완전히 이동시켜 다시 돌아오지 않는 경우에만 사용하고, 그 이외에는 모두 Link 태그를 사용하는 것이 좋습니다.

Link 태그 사용하기
import Link from "next/link";

const Index = () => (
  <div>
    <Link href="/blog">
      <a>Blog</a>
    </Link>
    // 동적 link시 [] 사용
    <Link href="/blog/[address]">
      <a>Blog</a>
    </Link>
  </div>
);

동적 url

Next.js는 가변적으로 변하는 url에 대해 동적 url을 지원하는데, {} 문법으로 동적 페이지를 생성하는 동적 url을 만들 수 있습니다.

다음의 코드의 경우 localhost:3000/123으로 접속하면 postid123으로 나오게 되는데, pages/[id].tsx 왼쪽 페이지 구조의 값은 router.query.{id}와 동일합니다.

pages/[id].tsx
import { useRouter } from "next/router";

export default () => {
  const router = useRouter();

  return (
    <>
      <h1>post</h1>
      <p>postid: {router.query.id}</p>
    </>
  );
};

prefetching

prefetching은 백그라운드에서 페이지를 미리 가져오는 기능으로, 기본값은 true로 되어있습니다. <Link /> 뷰포트에있는 모든 항목은 초기 또는 스크롤을 통해서 미리 로드되는데, 정적 생성을 사용하는 JSON 페이지는 더 빠른 페이지 전환을 위해 데이터가 포함 된 파일을 미리 로드합니다.

prefetching은 Link 컴포넌트를 사용해서 이뤄지는데, 링크 컴포넌트를 렌더링할때 <Link prefetch href="...">과 같은 형식으로 prefetch 값을 전달해주면 데이터를 먼저 불러온 다음 라우팅을 시작합니다. 참고로 prefetching은 프로덕션 레벨에서만 이루어지는 기능입니다.

next/router

Next.js의 router는 React의 react-router-dom의 사용 방법과 거의 유사하고, link에 있는 preferch 기능도 사용 할 수 있습니다.

next/router 사용하기
import { useEffect } from "react";
import { useRouter } from "next/router";
import posts from "../posts.json";

export default () => {
  const router = useRouter();

  const post = posts[router.query.id as string];
  if (!post) return <p>noting</p>;

  useEffect(() => {
    router.prefetch("/test");
  }, []);

  return (
    <>
      <h1>{post.title}</h1>
      <h1>{post.content}</h1>
      <button onClick={() => router.push("test")}>go to Test</button>
    </>
  );
};

getInitialProps()로 컴포넌트에 데이터 보내기

서버 사이드 렌더링을 하는 Next.js에서 컴포넌트는 각 페이지마다 사전에 불러와야할 데이터, 즉 data fetching이 필요합니다. react나 vue같은 클라이언트 사이드 렌더링 방식의 경우애는 useEffect, created 함수를 이용하여 data fetching을 하지만, 서버 사이드에서 실행하는 Next.js에서는 getInitialProps를 이용해 data fetching 작업을 하게 됩니다.

참고로 Next.js v9 이상에서는 getInitialProps 대신 getStaticProps, getStaticPaths, getServerSideProps을 사용하도록 가이드하고 있습니다.

server side lifeCycle

lifeCycle을 이해하기 위해서는 우선 getInitialProps를 조금 더 깊게 이해할 필요가 있지만, 간단하게 다음과 같은 프로세스로 정리할 수 있습니다.

  • Next.js 서버가 GET 요청을 받는다.
  • GET 요청에 맞는 pages/Component를 찾는다.
  • _app.tsx의 getInitialProps가 있다면 실행한다.
  • route에 맞는 페이지 Component의 getInitialProps가 있다면 실행하고, pageProps들을 받아온다.
  • _document.tsx의 getInitialProps가 있다면 실행하고, pageProps들을 받아온다.
  • 모든 props들을 구성하고, _app.tsx → page Component 순서로 렌더링한다.
  • 모든 Content를 구성하고 _document.tsx를 실행하여 html 형태로 출력한다.

head 태그 커스터마이징

Next.js는 페이지 제목을 커스텀하고 싶거나 meta 태그를 변경하고 싶을때, next/head로 부터 Head 컴포넌트를 받아 모든 컴포넌트에서서 커스터마이징을 할 수 있습니다.

head에 정보 추가하기
import Head from "next/head";

export default () => (
  <div>
    <Head>
      <title>새로 만들어진 타이틀 입니다</title>
    </Head>
    <div>...</div>
  </div>
);

Next.js는 해당 컴포넌트를 mount 할 때 Head 내부의 태그들을 페이지 HTML의 Head에 포함 시키고, unMount 할때 해당 태그를 제거하게 됩니다.

dynamic meta tag와 dynamic component import

dynamic meta tag

앞의 예시처럼 정적으로 태그를 다는 경우도 있지만, 정적으로 들어가야할 태그가 바뀌는 경우도 있는데, 이런 경우 Next.js는 동적으로 meta 태그에 값을 할당할 수 있습니다.

dynamic component import

dynamic component import는 react에서 lazy하게 component를 import 하는 방식과 유사한데, dynamic하게 처음에 보여주지 않아도 되는 컴포넌트는 import 하지 않기 때문에 초기 화면의 렌더링 속도를 높이고 싶을 때 사용지만, 만약 페이지에 진입할 때 맨처음 보이는 컴포넌트라면 dynamic 메소드를 사용할 이유는 없습니다.

component의 props 타입 정의하기
import React, { useState } from "react";
import dynamic from "next/dynamic";

const DynamicComponent = dynamic<{ nowTab: number }>(() =>
  import("./DynamicComponent")
);

const Index = () => {
  const [nowTab, setNowTab] = useState(0);

  return (
    <>
      {nowTab === 0 && <div>0 tab</div>}
      {nowTab === 1 && <DynamicComponent nowTab={nowTab} />}
    </>
  );
};

production 배포

production 배포는 다음의 명령어로 할 수 있는데, localhost:3000으로 접속해 배포된 버전을 로컬로 확인할 수 있습니다.

npm run build
npm run start

답글 남기기