React에서 Next.js 사용해보기

학습 목표

  • What is NextJS? And Why?
  • File-based Routing & Page Pre-rendering
  • Data Fetching & Adding an API

Introducing NextJS: Preparing React Apps For Production

What is NextJS? And Why?

NextJS는 생산용 React 프레임워크이다. 풀스택 프레임워크라고 생각해도 된다. React를 쉽게 구축할 수 있게 도와주는 역할을 한다. React에서 발생하는 문제를 다른 라이브러리 여러개 설치할 필요 없이 사용할 수 있어서 프레임워크라고 한다. 특히 Route에 대해서 도와준다고 한다.

  1. Server-side Rendering
    • 페이지 소스를 확인해보면 빈 HTML만 보이는데 이는 검색 엔진 크롤러가 인식하지 못하는 문제가 발생한다. 이를 해결 할 수 있다.
    • 페이지를 사전 렌더링한다.
  2. File-based Routing
    • 기존에는 페이지가 routing 되는 것처럼 보이는 작업이었다. 이해하기 쉬운 Routing 개념을 사용해 코드 대신 파일과 폴더의 pages와 routes를 정의한다.
  3. Fullstack Capabilities
    • 데이터 베이스에 접근하는 등 백엔드 코드를 쉽게 작성할 수 있다.

NextJS 공식 사이트

NextJS 시작하기

  1. Node.js 설치하기
  2. npx create-next-app
  3. pages의 index.js 파일에서 컴포넌트를 만든다.
  4. npm run dev로 개발 서버 시작
  • 오류 해결
    Parsing error: Cannot find module ‘next/babel’라고 뜨는 오류가 발생하였다. .eslintrc.json파일에서 대체만 해주면 된다.
    {
      "extends": ["next/babel", "next/core-web-vitals"]
    }
    

File-based Routing & Page Pre-rendering

파일 기반 라우팅을 사용하는 것은 쉽다. pages 폴더에 파일명이 주소 경로 이름이 되므로 파일만 생성해주면 알아서 서버측 렌더링을 해주고 SEO를 얻을 수 있다. 또한, 중첩으로 할 수 있으며 폴더도 경로에 포함된다.

그러나 만약 세부사항의 페이지 같은 경우 똑같은 경로로 하드코딩하면 문제가 생긴다. 이럴 때 동적 경로가 있는 페이지를 사용할 수 있다. 대괄호로 붙여주면 된다. 다시 말하지만 폴더나 파일이나 둘 다 사용 가능하다. 매개변수를 사용할 때는 useRouter() 훅을 사용하여 query에서 찾을 수 있다.

📂 pages
├── 📄 index.js (/)
└── 📂 news
    ├── 📄 index.js (/news)
    └── 📄 [newsId].js (/news/[newsId])
import { useRouter } from "next/router";

const DetailPage = () => {
  const router = useRoute();
  const newsId = router.query.newsId;

  // send a request to the backend API
  // to fetch the news item with newsId

  return <h1>The Detail page</h1>;
};

export default DetailPage;

경로 변경 버튼을 사용하고 싶을 때도 useRouter() 훅을 사용하면 된다.

const router = useRouter();

const showDetailsHandler = () => {
  router.push("/" + props.id);
};

페이지간에 연결을 하려면 a 태그 대신 Link 모듈을 사용하면 된다. a 태그를 사용하지 않는 이유는 HTML 파일을 재요청하기 때문에 SPA가 아니다.

import Link from "next/link";

const Home = () => {
  return (
    <ul>
      <li>
        <Link href="/">Home</Link>
      </li>
      <li>
        <Link href="/about">About Us</Link>
      </li>
    </ul>
  );
};

참고로 React 컴포넌트를 그대로 사용할 수 있다.

만약 레이아웃 컴포넌트로 모든 페이지를 동일하게 감싸고 싶다면 _app.js파일에서 작성하면 된다.

<Layout>
  <Component {...pageProps} />
</Layout>

Data Fetching & Adding an API

만약 pages에서 데이터를 들고 오려고 하면 useEffect()를 사용해야 한다. 근데 이는 컴포넌트가 렌더링 된 후에 실행되기 때문에 두 번째 사이클에 데이터가 들어온다. NextJS는 첫 번째 사이클만 반영하기 때문에 소스코드에는 비어있을 것이다.

이를 해결하기 위해서는 사전 렌더링의 두 가지 형태를 사용할 수 있다. 하나는 Static Generation이고 또 다른 하나는 Server-side Rendering이다. 일반적으로 정적 생성을 많이 사용한다고 한다.

정적 사이트 생성을 SSG라고 한다. 정적 생성에는 getStaticProps()를 사용하면 된다. 빌드 과정에서 실행되기 때문에 브라우저에서 접근할 수 없어 데이터 베이스에 접근해도 된다. 또한, 비동기를 지원하기 때문에 프로미스를 반환할 수 있다. 만약 데이터가 업데이트 되는 상황이라면 revalidate를 꼭 써야 한다. 아니면 오래된 값만 계속해서 보여서 매일 빌드 후 배포를 해줘야 하는 문제점이 생긴다.

export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
    revalidate: 1, // 1초마다 자동 갱신
  };
}

이 때, 빌드 과정에서 실행된다는 점 때문에 context.params에서 매개변수를 가져와도 작동하지 않을 것이다. 왜냐하면 빌드할 때는 매개변수에 뭐가 있는지 모르기 때문이다. 이를 위해서 getStaticPaths()함수를 사용해야 한다. fallback을 false로 두면 paths에서 정한 경로만 접근할 수 있고 나머지는 찾을 수 없다고 뜨게 하며 true로 두면 NextJS가 페이지를 만든다고 한다.

// Generates `/posts/1` and `/posts/2`
export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
    fallback: false, // can also be true or 'blocking'
  };
}

이 때 fallback에는 false, true, ‘blocking’을 사용할 수 있는데 false인 경우 paths가 반환하지 않은 모든 path는 전부 404 페이지를 반환한다. true인 경우 404페이지를 반환하지 않고, 빌드시 생성된 빈데이터가 있는 더미 사전 렌더링을 반환한다. ‘blocking’은 true와 유사하지만 더미 로딩 페이지를 반환하지 않으며 처음으로 렌더링 될 때까지 브라우저를 중단시킨다.

서버 측 렌더링은 SSR이라고 하며 getServerSideProps()를 사용하면 된다. 만약 데이터가 요청할 때만 업데이트 되었으면 한다면 이를 사용하면 된다. 이는 빌드 과정에서 실행되는 것이 아닌 deploy 이후에 실행된다. context에서 요청과 응답 객체를 받을 수 있다. 요청이 들어오기 전까지 페이지가 만들어지기 기다려야 한다는 단점이 있다.

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  };
}

만약 요청 객체에 접근할 필요가 없다면 SSG를 이용하는 것이 좋다. 그러나 자주 변경되는 데이터라면 SSR을 이용하는 것이 좋다.

이제 API를 추가해보자. NextJS는 API를 쉽게 추가할 수 있도록 도와준다. 이를 API Routes라고 한다. 먼저 pages 폴더에 api라는 폴더를 생성해야 한다. 이는 서버에서 돌아간다.

MongoDB를 설정해보자. Network Access에서 IP를 설정해주고 Database Access에서 user를 설정해준다. Database에 연결하는 방법은 Connect your application => npm install mongodb => import { MongoClient } from "mongodb" => MongoClient.connect() 함수 안에 your application code를 입력한다. 코드에서 ?retryWrites 앞에 원하는 데이터베이스 이름을 추가한다. => .db()로 접근한다. => .collection("meetups")로 컬렉션 생성 => .insertOne()으로 객체를 저장한다. 비동기 처리할 수 있다. 끝나면 .close()로 닫는다.

MongoDB - Collection Methods 문서

  • db.collection.find() : 모든 데이터 불러오기
    • find({}, {_id : 1})로 사용하면 모든 데이터의 _id 값만 가져온다. 공식문서에 따르면 _id는 기본으로 가져오는 것 같다. 그렇지만 명시적으로 적어서 알려주는 것도 나쁘지 않을 것 같다.
  • db.collection.insertOne() : 새로운 데이터 하나 넣는 것
  • _id 객체는 ObjectId로 되어있으므로 문자열로 바꿔서 사용할 수 있다.
  • db.collection.findOne() : 일치하는 것 하나만 반환

    • 이렇게 사용해야 id를 일치 시킬 수 있다
    import { ObjectId } from "mongodb";
    
    collection.findOne({
      \_id: ObjectId(meetupId),
      });
    

이는 절대로 클라이언트 사이드에서 작성되면 안된다. 내 정보가 들어있기 때문이다.

이제 fetch를 절대경로로 “api/new-meetup” 설정해 HTTP 요청을 똑같이 해주면 된다. 이 때, fetch는 브라우저, 서버 어디서든 작성 가능하다. 그리고 MongoDB에서 Collections에서 보면 데이터가 채워진 것을 확인 할 수 있다.

NextJS에서 .env 파일 사용하기

env.js로 했는데 그래도 .env 파일을 알아두면 좋지 않을까 해서 직접 찾아봤다.

  1. 최상단 폴더에 .env 파일을 만든다.
  2. 변수를 선언한다. API_KEY = "#####"
  3. Next.js는 자동으로 지원해주기에 그냥 사용하면 된다. (서버만 가능) process.env.API_KEY

참고로 환경을 설정할 수 있다.

  • .env : 모든 환경. 우선 순위 낮음.
  • .env.development : 개발 환경. .env 파일 덮어씀.
  • .env.production : 배포/빌드 환경. .env 파일 덮어씀.
  • .env.local : 모든 환경. 우선 순위 높음. .env.* 파일 덮어씀.

만약 브라우저 사이드에서 사용하고 싶다면 NEXT_PUBLIC_으로 시작하면 된다. 물론 서버에서도 사용 가능하다.

head meta 설정하기

<Head /> 컴포넌트를 사용하면 된다.

import Head from "next/head";

<Head>
  <title>React Meetups</title>
  <meta
    name="description"
    content="Browse a huge list of highly active React meetups!"
  />
</Head>;

Next.js 배포하기

직접 만든 서버에서 하려면 build 후 start 명령어를 치면 된다

다양한 방법이 있지만 우리는 Vercel을 사용해보자. Vercel은 NextJS를 개발한 팀과 같은 팀이 만든 호스팅 서비스 업체이기에 NextJS에 최적화가 잘 되어 있다.

  1. GitHub과 연동하기
  2. deploy하기

만약 다른 브랜치라면 Git에서 Production Branch 설정을 번경하면 된다. 그리고 하위 폴더라면 General에서 Root Directory 설정을 변경하면 된다.

  • deploy 오류
    • Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath ‘./lib/parser’ is not defined by “exports” in …
    • node 버전과 deploy 설정 되어 있는 node 버전이 달라서 생기는 문제이다. 프로젝트 설정에서 node 버전을 변경해주면 된다.
    • 이거 하다가 nvm으로 쉽게 node 버전 관리 할 수 있는 걸 알고 바로 설치해서 사용중이다.
      • nvm ls : 설치된 node 버전 목록
      • nvm install --lts : lts 최신 버전 설치
      • nvm install 18 : 18.x의 마지막 버전
      • nvm ls available : 가능한 목록
      • nvm use 16 : 16 버전으로 전환
      • nvm current : 현재 버전 확인
  • 자꾸만 Preview 배포가 되어서 다른 브랜치에서 커밋하는데도 deploy됨
    • Project Settings → Git → Ignored Build Step → [ $VERCEL_ENV != production ]
    • 참고 문서
    • 문제는… root directory를 설정을 해뒀더니 무시하기도 전에 directory가 없어서 오류가 뜬다.
      • 해결: 좀 웃기지만 그냥 github 연결 끊었더니 됐다…ㅎㅎ! 어차피 수정할 일 없을테니 이걸로 해결한 걸로 치겠다.

강의명

  • Udemy : React 완벽 가이드

카테고리:

업데이트:

댓글남기기