본 포스팅은 Inflearn(인프런) 이정환님의 한 입 크기로 잘라먹는 NextJS(15+) 강의를 참고하여 작성되었습니다.

[#1 - Page Router]

Page Router

Page Router는 말그대로 pages 폴더의 구조를 기반으로 Page Routing을 제공하는 구조를 말한다. UI를 그려주는 메인 파일명을 index라는 파일명으로 설정을 해주어야 한다. 아래 표는 NextJS 14버전부터 사용 가능한 App Router와 이전 버전의 Page Router을 간단하게 비교해둔 표이다.

App Router Page Router
app 폴더(최상위 폴더) page 폴더(최상위 폴더)
page 파일(UI) index 파일(UI)
- _app 파일(React의 App.jsx 파일 역할)
- _document 파일(React의 index.html 파일 역할)
layout 파일(레이아웃) _app 파일에 작성
not-found 파일(에러) 404 파일(에러)

 

아래의 npx 명령어를 통해서 NextJS 프로젝트를 시작할 수 있는데, 별도의 추가 기능에 대한 질문이 나온다. 본인이 사용하고자하는 기능이 있으면 추가하여 NextJS를 설치 후 프로젝트를 진행하면 된다.

npx(node package executor) create-next-app@14 {FileName}
Use TypeScript
Use ESLint
Use TailwindCSS
Use 'src/' directory
Use import alias customizing

 

_app.tsx

_app 파일은 React에서 모든 컴포넌트들의 상위 컴포넌트 역할을 했던 App 파일과 같이 모든 Page Component들의 부모 역할을하는 Next App의 Root Component로 내부에 공통 컴포넌트나, Layout을 작성하거나 로직을 구현하는 것이 가능한 파일이다.

 

_document.tsx

_document 파일은 모든 페이지에 공통적으로 적용이 되어야하는 Next App의 HTML 코드를 설정하는 Component로 React에서 index 파일과 동일한 역할을 한다. _app 파일과 동일하게 모든 Component에 적용이되므로 meta Tag, Font, Charset, Google Analytics 같은 서드 파티 스크립트 등 페이지 전체에 적용되는 HTML 파일을 관리하는 파일이다.

 

next.config.mjs

Next App 전체의 설정 파일이다.

[#2 - Settings Page Router]

Page Router Setting

pages 폴더 하위에 특정 파일을 생성하면 해당 파일 이름으로 Routing이 자동적으로 동작하게 된다. 예를 들어 Search.tsx라는 파일을 생성하였다면 메인 url에 /search만 붙여준다면 Search.tsx로 생성한 페이지로 이동이 가능하게 된다.

파일이 아닌 폴더를 생성하고 내부에 index 파일만 존재한다면 해당 폴더명으로 url 주소 접근이 가능하며 마찬가지로 Routing 구현이 가능하게 된다.

 

Query String

이 때 특정 폴더명을 통해서 해당 페이지로 접근을 하는 동시에 Query String이 포함된 url 주소로 접근을 하는 경우도 발생한다. Query String이란 사용자가 검색한 정보에 따라 해당 정보를 url 주소에 포함시켜 해당 페이지로 전환을 시켜줄 수 있는 정보를 의미한다. 대부분 url 주소 마지막에 "?"가 붙고 그 이후에 나오는 정보들이 Query String이다.

Query Stringnext/router에서 제공하는 useRouter 훅을 통해서 객체에 접근이 가능하다. 객체 내부에 query 속성을 통해서 사용자가 url 주소에 함께 입력한 Query String 정보에 대해서 가져올 수 있다.

예를들어 "localhost:3000/user?page=12" 와 같은 형태로 사용자가 입력하였을 경우 "?"를 기준으로 뒤에 작성된 부분은 Query String이라고하며 해당 내용은 useRouter로 생성된 객체에 query 속성에 page를 key 값으로, 12를 value 값으로 저장이 되는 것이다.

여기서 참고로 NextJS는 url 주소를 읽는 과정에서 query String이 존재하면 컴포넌트를 불러오는 과정에서 한 번 더 컴포넌트를 호출하게 된다.

 

Dynamic Routing(동적 라우팅) / useRouter

url 주소 뒤에 동적으로 변하는 주소값을 url parameter라고 한다. url parameter가 붙으면 같은 주소 내에서 동적인 라우팅이 설정이 가능하다. 예를 들어 블로그의 게시글이나 쇼핑몰의 상품 같은 경우 각 페이지 별로 상위 주소는 동일하지만 서로 다른 parameter 값을 갖고 있는 것을 볼 수 있다. "/item/12", "/page/24"와 같은 형태이다. 이런 페이지들은 해당 parameter 값에 따라 일일히 페이지를 생성해주는 것이 아닌 동적으로 Routing을 구현하는 것인데 구현 방식은 15버전과 동일하다. parameter에 해당하는 값의 이름을 대괄호를 씌워서 파일명으로 설정해주는 것이다. [id].tsx와 같은 형태이다. 이 때 useRouter을 통해서 query String과 동일하게 객체의 query 속성을 보게 되면 url parameter 값이 존재하는 것을 볼 수 있게 된다.

여기서 주의해야 할 점은 Query String과 URL Parameter 값이 모두 존재할 때 useRouter 객체의 query 속성에는 어떻게 부여되는지이다. Query String은 key와 value 형태로 저장이 되며 url Parameter는 id를 key로 갖고 parameter 값은 "/" 기준으로 나뉘어 배열 형태의 value로 저장되게 된다.

만약 url Parameter 값이 굉장히 많아지게 되면 parameter 값은 "/" 기준으로 나뉘어 배열 형태로 value로 저장되게 된다. 이 때는 파일명을 변경해주어야 하는데 Catch All Segment라고 해서 모든 구간을 잡아낸다라는 의미로 [...id].tsx와 같이 변경해주어야 한다.

마지막으로 동적 라우팅을 구현한 페이지에는 query String이나 url Parameter가 없는 경우의 페이지는 구현되어있지 않은데 아무 요소도 없는 페이지를 구현하기 위해서는 Optional Catch All Segment라고 해서 파일명을 대괄호로 한 번 더 감싸서 [[...id]].tsx로 변경해준다면 아무 요소가 없는 url 주소로도 페이지가 생성되게 된다.

오류(에러) 페이지 또한 자체적으로 Customizing이 가능하다. 에러 페이지는 404 Page로 알려져있기 때문에 파일명을 404.tsx로 설정한 후 내부 컴포넌트를 작성하면 Next App 내에서의 오류나 에러가 발생하였을 때 자동적으로 404 Page가 뜨게 된다.

 

[#3 - Navigating]

Navigate

우리가 흔히 알고 있는 페이지 이동 방식을 살펴보면 anchor 태그를 이용해서 이동하고자 하는 페이지를 새로이 요청하면서 이동하는 방식이 존재하고 React-Router-Dom에서 사용했던 Link 태그와 to 속성을 이용해서 이동하는 방식이 있는 것을 알 수 있다. anchor 태그는 페이지 이동 간에 매번 새로운 요청을 보내기 때문에 속도 저하와 불필요한 요청으로 인한 비효율적인 방식이다. CSR을 통해서 페이지 이동을 하기 위해서 Next App에서도 React와 마찬가지로 Link 태그를 이용해서 Navigate를 구현한다. Next14 이전 버전에서는 next/navigation이 아닌 next/link가 제공해주는 Link Component를 통해서 구현하는 것이 좋다.

Link Component는 React에서의 Link 태그와 사용 방법이 동일하지만 React에서는 to 속성을 사용하여 이동 경로를 지정해주었다면 Next App에서는 a 태그와 동일하게 href 속성을 이용해서 경로를 지정해준다.

또한 Link Component를 통해서 Navigator Bar를 구현하는 것이므로 _app 파일의 컴포넌트 내부에 작성해주는 것이 바람직하다.

 

useRouter / push,replace,back

위와 같이 Navigator Bar을 생성하여 페이지 이동을 편리하게 할 수 있는 UI를 구현하는 방법도 있지만 특정 이벤트나 특정 조건이 만족함에 따라 페이지를 이동시켜주는 Programmatic Navigation도 구현이 가능하다. 대표적인 예로는 Link Component가 아닌 특정 버튼을 눌렀을 때 이벤트를 감지하고 페이지를 이동시켜주는 방식이라고 생각하면 쉬울 것 같다.

이 때 사용하는 Hook이 존재하는데 앞서 살펴보았던 next/router의 useRouter이다. useRouter의 push method를 통해 링크를 전달해주면 페이지 이동이 가능하게 된다. 간단하게 코드 형식을 살펴보면 다음과 같다.

import {useRouter} from 'next/router';

export default function App({Component, pageProps}: AppProps) {
  const router = useRouter();
  const onClick = () => {
    router.push('/detail');
  }
  return (
    <div>
      <button onClick={onClick}>Move to Detail Page</button>
      <Component {...pageProps} />
    </div>
  )
}

button 태그에 onClick 키워드를 통해 클릭 이벤트가 발생하면 useRouter의 push method에 '/detail' 경로를 전달해주어 detail 페이지로 이동할 수 있도록 구현한 로직이다.

push method 외에도 replace와 back method도 존재한다. replace뒤로가기를 방지하며 페이지를 이동하는 method이고 back은 페이지 뒤로가기 method이다. 사용 방법은 동일하다.

 

[#4 - Pre-Fetching]

Pre-Fetching

Pre-Fetching이란 사용자의 보다 빠른 페이지 이동을 위해 제공되는 기능으로 사용자가 현재 위치해 있는 페이지에서 이동할 가능성이 있는 페이지에 대한 정보들을 미리 호출을 해놓아 실제 사용자의 이동 요청이 발생하였을 때 더 빠른 속도로 이동을 시켜주는 기능을 의미한다.

 

NextJS는 Next App에 작성해두었던 React Component들을 자동으로 페이지 별로 분리하여 저장을 해둔다. 따라서 Pre-Rendering 과정에서 현재 사용자가 위치해 있는 페이지에 대한 JS파일만을 Bundle로 생성하여 사용자에게 보내주는 것이다. 그러므로 사용자가 다른 페이지로 이동을 하였을 경우에는 이동한 페이지의 React Component들이 담긴 JS파일 번들러를 다시 다운로드 받아서 보내주어야 한다는 것이다. 예를 들어 /search 페이지로 접속 요청을 보냈을 때는 Search JS Bundle 파일만 다운을 받아서 렌더링을 해주는 것이고 /user 페이지로 이동 요청을 보낸 경우에는 그 때 User JS Bundle 파일을 다운받아서 이동 요청에 맞게 페이지를 렌더링 해주는 것이다.

 

NextJS가 이렇게 동작하는 이유는 만약 사용자의 첫 요청에 모든 페이지의 JS Bundle 파일을 전달해주는 경우 파일 용량이 매우 커지게 되면서 Hydration 과정이 오래걸리기 때문에 TTI(Time To Interface)의 시간이 늘어나기 때문이다.

 

Pre-Fetching의 동작 원리를 간단하게 살펴보면 사용자가 초기 접속 요청을 보낸 페이지를 렌더링 해주는 과정에서 초기 페이지에 포함되어있는 링크나 버튼 태그들을 살펴보고 해당 요소들로 인해서 이동이 가능한 페이지가 존재한다면 이동할 가능성이 있는 페이지는 모두 미리 불러오는 것이다. 쉽게 표현하자면 이동 가능한 범위 내의 페이지를 모두 다운 받아놓았기 때문에 페이지 이동 요청이 발생하더라도 빠르게 페이지 이동 처리를 해줄 수 있게 되는 것이다.

 

Pre-Fetching은 개발 모드가 아닌 프로젝트가 빌드 된 Production 환경에서 확인이 가능하다. 따라서 진행중인 프로젝트의 터미널에서 npm run build를 통해 프로젝트를 build한 후 npm run start를 통해 운영 환경에서 서버를 동작시킨 후 개발자 도구의 network 탭을 통해 Pre-Fetching을 확인 가능하다. network 탭을 살펴보면 현재 존재하는 페이지 이외에도 Link Component로 연결되어있는 페이지들에 대한 정보까지 다운로드가 되어있는 것을 확인할 수 있을 것이다.

 

여기서 참고해야할 점이 있는데 일반적으로 Link Component를 통한 페이지 이동이 아니라 버튼의 이벤트 발생에 따른 페이지 이동 같은 경우는 Pre-Fetching이 적용되지 않는다. 이런 경우는 페이지가 Mount 되는 시점에 맞게 useRouter 훅을 통해 Pre-Fetching을 수동으로 시켜주는 방법을 사용해야 한다. 페이지의 Mount 되는 시점은 useEffect 훅에 빈 의존성 배열을 전달해줌으로써 맞출 수 있고 useEffect 훅 내부에 useRouter의 prefetch method를 통해 Pre-Fetching하고자하는 url parameter을 전달해주면 Link Component가 아닌 페이지 이동 요소에도 Pre-Fetching을 적용시켜줄 수 있다.

 

그렇다면 반대로 Link Component로 작성된 요소의 Pre-Fetching을 방지하는 방법은 무엇일까. Link Component에 prefetch={false} 속성을 넣어주면 해당 Link Component는 Pre-Fetching이 동작하지 않게 된다. 코드 형태는 아래와 같은 형태로 작성되게 된다.

import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";

export default function App({ Component, pageProps }: AppProps) {
  const router = useRouter();
  const handleClick = () => {
    router.push('/test'); //클릭 이벤트에 의한 페이지 이동
  }
  useEffect(() => {
    router.prefetch('/test'); 
  },[])
//페이지가 mount 되는 시점에 클릭 이벤트에 의해 페이지 전환이 이루어지는 요소에 대한 prefetching 기능 부여
  return (
    <>
      <header>
        <Link href={"/"}>Home</Link>
        <Link href={"/search"} prefetch={false}>Search</Link> {/*Prefetching 방지 속성 적용*/}
        <Link href={"/book"}>Book</Link>
        <div>
          <button onClick={handleClick}>Move to Test Page</button>
        </div>
      </header>
      <Component {...pageProps} />
    </>
  );
}

 

[#5 - API Routes]

API Routes

API Routes란 NextJS에서 API를 구축할 수 있게 해주는 기능이다. Pages 폴더 하위에 api 폴더 내에 생성된 파일들은 웹페이지가 아닌 API Routes로서 API 응답을 정의하는 파일로 자동 설정이 된다. 또한 폴더와 파일의 경로와 동일한 경로를 갖게 된다. 예를 들어 api 폴더 내에 time.ts라는 파일을 생성한 후 특정 로직을 구현하면 NextJS가 자동으로 API로 생성을 해주며 localhost:3000/api/time이라는 경로로 API 요청을 보내게되면 구현한 로직에 따라 함수가 실행되면서 데이터를 반환받을 수 있게 되는 것이다.

 

간단하게 현재 시간을 반환하는 API를 구현하는 예시를 보자. 아래와 같이 간단하게 구현이 가능하다.

import type {NextApiRequest, NextApiResponse} from 'next';

export default function Time(
  req: NextApiRequest,
  res: NextApiResponse
){
  const date = new Date();
  res.json({time: date.toLocalString()})
}

위와 같이 생성된 time.ts 파일을 통해 서버를 구동하여 localhost:3000/api/time에 접속하게 되면 다음과 같이 반환한 데이터를 확인할 수 있다.

 

이 때 req,res의 타입인 NextApiRequest와 NextApiResponse는 NextJS에서 제공해주는 API 요청과 응답의 타입으로 import type을 통해 사용하면 된다.

 

[#6 - Styling in NextJS]

React에서는 특정 파일의 CSS 파일을 동일한 파일명으로 생성한 후 import 해서 사용하곤 했었다. 하지만 NextJS에서는 _app 파일의 App 컴포넌트를 제외한 다른 컴포넌트들은 별도로 생성된 CSS 파일을 그대로 import하는 것은 불가능하다. 그 이유는 서로 다른 두개의 CSS 파일에 공통된 클래스 명이 존재하게 되는 경우 두 파일이 동일한 폴더에 속해 있는 경우 클래스명 충돌이 발생하여 스타일 오류가 발생할 수 있기 때문이다. 따라서 CSS 파일을 그대로 불러와 사용하는 것은 불가하고 이런 경우 NextJS에서 기본적으로 제공하는 CSS Module 방식을 사용하면 된다. CSS Module을 사용하면 NextJS 자체적으로 클래스명을 다른 파일의 클래스명과 중복되지 않도록 무작위로 설정해주는 방식을 통해 클래스 작명에 난관을 겪을 필요가 없어진다.

 

정리하자면 NextJS에서는 CSS의 클래스명 중복의 우려를 원천차단하기 위해서 일반 CSS 파일은 _app 파일 외에는 적용이 불가능하도록 동작하기 때문에 별도의 Component들에는 CSS Module 파일을 생성하여 해당 모듈 파일을 import 해서 사용해야 한다. 폴더 구조와 파일명의 예시를 사진과 함께 살펴보자.

Style Module

위의 사진을 보면 알 수 있듯이 tsx 파일이 존재하고 해당 파일에 대한 스타일을 적용할 수 있는 CSS 파일을 CSS Module 형식으로 저장하여 사용하는 것을 볼 수 있다. CSS Module 파일들은 별도의 styles 폴더에 _app 파일에 적용되는 global.css 파일과 함께 CSS 파일끼리 모아서 관리하는 방법도 있고 해당 CSS Module 파일이 적용되는 Component 파일과 파일명이 비슷하기 때문에 위의 사진처럼 같은 경로에 저장하여 관리하는 방법도 존재한다. 이 부분은 선택적이며 회사나 팀에 따라 설정하면 될 것 같다.

 

[#7,8 Global Layout & Page Layout]

Global Layout

프로젝트를 진행할 때 Naviagte Bar나 웹페이지의 Footer 같은 요소들은 일부 페이지가 아닌 전체 페이지에 적용을 시켜주어야 한다. React로 프로젝트를 진행할 때에는 각각에 해당하는 Component들을 생성한 후 별도의 layout 파일을 생성한 후 Outlet Component와 함께 Layout을 적용해주었었다. NextJS에서는 최상위 컴포넌트인 _app 파일의 App 컴포넌트에 Global Layout을 적용해주면 된다.

 

방법은 간단하다. Global Layout에 해당하는 파일과 컴포넌트를 별도로 생성해준다. 이후 전역적으로 적용이 될 요소들을 컴포넌트 내에 구현을 해주는 것이다. Header, NavBar, Footer 등의 요소들을 생성해 준 후 Global Layout 컴포넌트를 _app 파일에 import 하여 App 컴포넌트가 받는 Component들의 외부에 Layout을 적용시켜주는 것이다. 말로 설명하면 복잡하고 어려우니 간단하게 코드 예시로 확인해보자.

Global Layout
_app

위의 사진을 보면 알 수 있듯이 GlobalLayout이라는 컴포넌트를 별도로 생성한 후에 App 컴포넌트에 import 해주면서 App 컴포넌트가 받는 많은 하위 컴포넌트들을 GlobalLayout이 함께 하위 컴포넌트로 받아 모든 페이지에 Layout이 적용이 되는 것이다. 이러한 과정에서 React 요소들은 ReactNode라는 타입을 정의 받는다.

 

Page Layout

위의 Global Layout 적용에 이어서 전역적인 Layout 적용이 아닌 특정 페이지에만 적용하고 싶은 Layout도 존재할 것이다. Page Layout은 좀 생소한 방식으로 적용이 된다. 사실 NextJS 14버전 이후부터는 폴더 구조를 통해 Router을 생성해 둔 후 각 페이지에 적용하고자 하는 Layout은 layout.tsx라는 파일을 생성하여 적용하면 Page Layout이 쉽게 구현이 되지만 현재 학습중인 NextJS는 13이전 버전이기 때문에 생소한 방식으로 적용을 해야 한다.

 

앞서 첨부된 사진을 보면 App 컴포넌트 내에 getLayout이라는 함수가 사용되고 있는 것을 볼 수 있다. getLayout이란 NextJS에서 일부 페이지에만 적용하고자 하는 Layout이 있을 때 Layout Component를 생성한 후 적용하고자 하는 컴포넌트에 getLayout method를 사용하여 컴포넌트를 반환해주는 형식이다. 예시 코드를 살펴보자.

Page Layout

위의 사진은 일부 페이지에만 사용될 Page Layout을 생성해둔 Component이다. 예를 들어 본 Component가 Home Component에만 Layout이 적용이 되야하는 경우 Home Component에는 getLayout을 사용하여 다음과 같은 로직이 작성된다.

Home.tsx

Component 외부에 별도의 Method를 사용하는 문법 자체가 굉장히 생소하지만 JS 문법에 따르면 함수 또한 객체이기 때문에 함수에 method를 적용하는 것이 이상하지만은 않다. 앞서 살펴본 App 컴포넌트에 Global Layout을 씌워준 형태처럼 Page Layout을 적용해주고자 하는 컴포넌트에 method를 사용하여 Page Layout을 컴포넌트 겉에 씌워준 후 반환해주면 최상위 컴포넌트인 App 컴포넌트에서 getLayout method의 매개변수로 하위 컴포넌트들을 받아서 처리해주면 Page Layout이 적용이 되는 것이다. 앞의 _app 파일 코드 사진을 보면 Page Layout이 어떤 과정을 통해서 적용이 되었는지 확인 할 수 있을 것이다.

'NextJS' 카테고리의 다른 글

[NextJS] - (4) SEO, Deploy  (6) 2024.09.19
[NextJS] - (3) SSR,SSG,ISR  (1) 2024.09.19
[NextJS] - (1) NextJS  (1) 2024.09.10
[NextJS(v.14)] - (3) Data Fetching  (0) 2024.08.20
[NextJS(v.14)] - (2) Routing  (2) 2024.08.16

+ Recent posts