Next.js 사용 경험?

이전에 Next.js를 애플코딩의 강좌로 빠르게 훑어보고 토이프로젝트로 블로그까지 만들어본 경험이 있고, 아래는 그 경험들을 포스팅한 목록이다. 토이프로젝트인 블로그를 배포도 해봤는데, 비용 이슈로 폐쇠하였다.


공부방법

(이 강의를 추천해준 친구에게 감사를 표하는 바이다)


1. 렌더링 방식

  • CSR(Client Side Rendering): 브라우저가 렌더링. 서버로부터 HTML 파일을 먼저 받고, React 라이브러리와 Javascript를 다운로드 받아 페인팅 함
    • 장점: 한번 로딩 되면, 그 다음부터 빠르게 사용할 수 있음, 서버의 부하가 적음
    • 단점: 로딩이 오래 걸림, 구글 크롤러와 같은 SEO가 어려움
  • SSG(Static Site Generation): 서버가 렌더링. 서버에서 HTML, React 라이브러리, Javascript를 미리 빌드해놓는다. 클라이언트 요청 시 전달
    • 장점: 로딩이 빠름, SEO가 좋음
    • 단점: 데이터가 정적이기 때문에 동적인 서비스에 좋지 못함
  • ISR(Incremental Static Regeneration): 서버가 렌더링. 빌드를 주기적으로 진행한다.
    • 장점: SSG의 장점 + 데이터가 주기적으로 바뀜
    • 단점: 완전한 실시간 데이터 제공은 어려움
  • SSR(Server Side Rendering): 브라우저가 렌더링. 클라이언트가 요청할 때 빌드해줌.
    • 장점: SSG와 ISR의 장점 + 실시간 데이터 사용할 수 있음
    • 단점: 서버 부화

2. ‘하이드레이션’ 이란?

하이드레이션이란, 수분을 공급한다 정도의 의미인데, Next.js에서는 SSR, SSG와 관련된 용어이다. 이는 브라우저에서 Javascript가 페이지의 서버에서 렌더링된 HTML을 가져와서 리액트 컴포넌트로 “활성화”하는 과정을 말한다. 과정을 설명하자면,

  1. 서버에서 페이지가 렌더링된다. 이렇게 생성된 HTML은 브라우저에 전송된다
  2. 브라우저에서 이 HTML은 초기 화면을 표시하는데 사용된다. 이 시점에서 페이지는 아직 정적이다
  3. Javascript가 로드되고 실행된다. 이 때, React는 이미 있는 HTML을 재사용(즉, 다시 렌더링하지 않고)하여, 해당 페이지에 대한 React 컴포넌트를 초기화한다. 이 과정을 ‘하이드레이션’이라고 한다
  4. 이제 페이지는 React와 상호작용하는 동적 페이지로 변환되어 사용자와의 상호 작용에 반응할 수 있게 된다

3. 프로젝트 생성

npx create-next-app@latest

image-20230905122601386


4. 프로젝트 구조

image-20230905135408975


5. 프로젝트 실행

프로젝트 실행 전, package.json 파일의 scripts에 어떠한 명령어들이 있는지 확인하자

// root > package.json

... 생략 ...

"scripts": {
  "dev": "next dev", // 1. 개발 모드
  "build": "next build", // 2. 빌드
  "start": "next start", // 3. 빌드  실행할 
  "lint": "next lint" // 4. 소스코드 검사
},

... 생략 ...

1번. local에서 개발할 때 사용하는 명령어

2번. 서버에 배포한 뒤에 프로젝트를 빌드할 때 사용하는 명령어

3번. 서버에 배포한 뒤에 빌드 후 실행할 때 사용하는 명령어

4번. next lint로 소스코드를 검사하기 전에 VSCode의 EsLint 익스텐션을 설치하여 검사하자


개발 모드로 실행했을 때, 터미널 명령어

npm run next dev

6. 라우팅

Next.js만의 파일단위로 라우팅할 수 있는 기능은 막강하고 매력적이고, Next.js를 사용하게끔 하는 이유이기도 하다. 정적라우팅과 동적 라우팅에 대해서 다루어보자

6-1. Next 12 버전) 정적라우팅(1)-단일 주소

순서 1. 파일단위로 라우팅하기 위해, src에 pages 경로를 생성한다. 이 때, pages 이외에 다른 이름을 선택하면, 라우팅되지 않으니 주의하자

cd src
mkdir pages

순서 2. pages에서 .tsx파일을 생성한다. 이 때, 생성한 파일명이 주소명이 된다. 단, export로 내보내진 함수명의 이름과 주소명은 관계가 없다

cd pages
touch about.tsx

image-20230905145216214

image-20230905145246106


6-2. Next 12 버전) 정적라우팅(2)-중첩 주소

예를 들어, {베이스 주소}/fruits 와 {베이스 주소}/fruits/orange 와 같은 중첩 주소를 생성하고 싶은 경우, 아래와 같이 경로와 파일을 생성하면 된다

순서 1. pages에 fruits 경로를 만들고, index.tsx파일을 생성하여 fuirts 경로로 접근했을 때 보여지는 기본 화면을 만든다. 여기서 index.tsx는 해당 경로에 접근했을 때 처음에 보여지는 컴포넌트이다.

cd pages
mkdir fruits
cd fruits
touch index.tsx
// pages > fruits > index.tsx

export default function index() {
    return <h1>This is fruits Index!</h1>
}

image-20230905150811668


순서 2. fruits 경로 아래에 orange.tsx 파일을 생성하면, 중첩주소({베이스 주소}/fruits/orange)로 라우팅 된다

touch orange.tsx
// pages > fruits > orange.tsx

export default function orange() {
    return <h1>This is Orange Page!</h1>
}

image-20230905151234096

pages에서 파일 단위로 경로를 관리하면 발생하는 문제

  • pages에서 재사용하고 싶은 파일이 있다면, 주소에 영향을 주기 때문에 곤란함
  • 이러한 문제를 개선하기 위해, Next 13 이후부터 app을 사용하기 시작했다

6-3. Next 13 버전) 정적라우팅(1)- app을 이용한 단일 주소

Next 12 버전에서 pages 경로에 파일을 생성하는 식으로 라우팅을 관리했다면, Next 13 버전에서는 {디렉터리}와 {page.tsx}를 쌍으로 생성해야만 해당 주소의 페이지를 볼 수 있다

순서 1. {베이스 주소}에 접속했을 때, 보여지는 페이지를 수정하려면 app > page.tsx를 수정한다

// src > app > page.tsx

export default function Home() {
  return (
    <h1>This is firstPage!</h1>
  )
}

image-20230905154510785


순서 2. {베이스 주소}/animal 주소로 페이지를 보여주려면, app 하위에 animal 경로를 생성하고, animal 경로에 page.tsx를 생성하면 된다

// app > animal > page.tsx

import Image from 'next/image'

export default function Home() {
    return (
        <h1>This is animal Page!</h1>
    )
}

image-20230905154856212


6-4. Next 13 버전) 정적라우팅(2)- app을 이용한 단일 주소

순서 1. {베이스 주소}/products 와 {베이스 주소}/products/pants 를 생성하려면 먼저 app 하위에 products 경로를 생성하고, products 경로 하위에 1. page.tsx 와 2. pants 경로를 생성한다.

image-20230905155645590

// app > products > page.tsx

export default function Index() {
    return (
        <>
            <h1>This is our products Index</h1>
            <h2>pants</h2>
            <h2>hats</h2>
        </>
    )
}

image-20230905160211204


순서 2. {베이스 주소}/products/pants 주소를 생성하려면, products 경로 하위에 page.tsx 파일을 생성한다

// app > products > pants > page.tsx

export default function Pants() {
    return (
        <>
            <h1>This Pants Page!</h1>
        </>
    )
}

image-20230905160201918


6-5. 동적라우팅(1)

예를 들어, {베이스 주소}/flowers/rose 에서 rose를 받아오거나, {베이스 주소}/flowers/lily 에서 lily를 받아와서 해당 정보로 조회하여 화면에 뿌려야하는 경우가 있는데, 이럴 때 동적라우팅이 필요하다

순서 1. app 하위에 flowers 경로를 생성한다. 그리고 하위에 [slug] 경로를 생성하는데 이 때, 기억할점은 1. 대괄호, 2. 주소명이다. 주소명은 아무거나 작성해도 되지만, 대부분의 경우 slug라는 이름을 사용한다고 한다.

마지막으로 [slug] 하위에 페이지로 page.tsx 파일을 생성한다

// app > flowers > [slug] > page.tsx

export default function flowers({ params }: any) { // 1. params 서브 주소값을 props로 받아오기
    console.log(params);

    return (
        <>This is flowers page!</>
    )
}

1번. 예를 들어, {베이스주소}/flowsers/rose 를 입력하면, 터미널에 아래와 같은 메시지가 출력된다. 타입은 출력을 위해, any로 설정

{ slug: 'rose' }

순서 2. type을 정의해서 서브주소명을 받아낼 수도 있으니 참고하자

// app > flowers > [slug] > page.tsx

type Props = { // 1. 타입 정의
    params: {
        slug: string;
    }
}

export default function flowers({ params }: Props) {
    console.log(params.slug); // 수정 1

    return (
        <>This is {params.slug} page!</> // 수정 2
    )
}

1번. 서브주소명이 담긴 props의 타입 정의. 타입정의의 예약어는 type


순서 3. 브라우저에 {베이스주소}/flowsers/rose 와 {베이스주소}/flowsers/lily를 입력했을 때, 아래와 같이 나온다면 성공

image-20230906090323869

image-20230906090326692


6-5. 동적라우팅(2) - 정적으로 경로를 생성해야하는 경우

generateStaticParams 함수는 빌드 시, 클라이언트가 요청한 경로를 생성하는 방식에서 동적 라우트 세그먼트와 함께 사용하여 정적으로 경로를 생성하는 데 사용할 수 있다 (참조: https://nextjs.org/docs/app/api-reference/functions/generate-static-params)

예를 들어, 실무에서 상품 DB에서 각 상품이 고유한 ID나 슬러그를 가지고 있고, 동적주소로 접근할 수 있을 때, 이러한 상품들의 페이지를 빌드 타임에 생성하면 사용자에게 빠르게 제공할 수 있다

// app > flowers > [slug] > page.tsx

... 생략 ...
export function generateStaticParams() {
    const flowers = ['sunflower', 'tulip']; // 1. 2개의 정적 페이지
    return (
        flowers.map((flower) => ({
            slug: flower
        }))
    );
}

1번. {베이스주소}/flowsers/ 는 동적인 주소임에도, 빌드했을 때, 아래와 같이 2개의 페이지 구조가 만들어져잇는 것을 확인할 수 있다.

image-20230906094527779


7. 404 페이지 커스텀하기

사용자가 요청한 주소가 없는 경우, 404 에러를 캐치해서 커스텀한 에러 페이지를 라우팅할 수 있다. 이 때 필요한 것은 아래와 같다.

  • 커스텀 에러 페이지를 적용할 경로에 not-found.tsx 파일을 생성
  • notFound() 함수 추가

순서 1. {베이스 주소}/flowers/nothing 입력 시, 커스텀한 에러페이지를 띄우기 위해, flowers 경로 하위에 not-found.tsx 파일을 생성하고, 페이지를 작성한다

// app > flowers > not-found.tsx

export default function NotFound() {
    return (
        <h1>찾으시는 페이지가 없습니다.</h1>
    );
}

순서 2. [slug] > page.tsx 의 함수에 notFound() 함수를 추가한다

// app > flowers > [slug] > page.tsx

import { notFound } from "next/navigation"; // 추가

... 생략 ...
export default function flowers({ params }: Props) {
    if (params.slug === "nothing") notFound(); // {베이스주소}/flowers/nothing 접근 시, 에러페이지

    return (
        <>This is {params.slug} page!</>
    )
}

image-20230906103231113


순서 3. 테스트하기

image-20230906103300507


8. app > layout.tsx 파일의 정체

app > layout.tsx는 일반적으로 페이지의 공통 구조나 스타일을 담당하는 컴포넌트이다. 예를 들어, 헤더, 푸터, 사이드바와 같은 요소들을 포함할 수 있다. 이렇게 하면 각 페이지에서 이러한 공통 요소를 반복해서 정의할 필요가 없다. 실습을 위해 아래와 같은 구조로 페이지를 보여줄 계획이라 가정하겠다

image-20230906104647382

순서 1. app > layout.tsx 파일의 태그를 아래와 같이 수정한다. 여기에서 children은 경로 접속 시, 보여주는 페이지다. 여기서 주의할 점은 태그(html, body)가 없으면, 에러가 발생하니 참고하자

// app > layout.tsx

... 생략 ...
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <header>나는 헤더다</header>
        <main>{children}</main>
        <footer>나는 푸터다</footer>
      </body>
    </html>
  )
}

순서 2. 어떠한 경로로 접속하더라도, layout.tsx에 설정한 헤더와 푸터가 고정으로 보이고 그 사이에 접속한 페이지가 보여지는 걸 확인할 수 있다

image-20230906110753603

image-20230906110800623


9. layout.tsx 파일에 cSS 스타일 적용하기

순서 1. layout.tsx파일과 동일한 위치에 layout.module.css 파일을 생성하여 스타일한다. 이때, {파일명}.moudle.css 를 꼭 붙여줘야 한다

// app > layout.module.css

.header {
    background-color: red;
}

.footer {
    background-color: blue;
}

순서 2. layout.tsx 파일을 아래와 같이 수정해준다

// app > layout.tsx

... 생략 ...
import styles from './layout.module.css'; // 1. 임포트

... 생략 ...
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {/* 2. className 체이닝 */}
        <header className={styles.header}>나는 헤더다</header>
        <main>{children}</main>
        <footer className={styles.footer}>나는 푸터다</footer>
      </body>
    </html>
  )
}

1번. css 파일을 styles 이름으로 임포트한다. 이 때, css에서 지정했던 클래스네임을 styles를 통해 불러올 수 있다

2번. className을 어떻게 지정했는지 잘살펴보자. css 파일을 체이닝할 때, 태그의 className 또는 id를 사용한다


순서 3. 테스트

image-20230906112603400

image-20230906112609672


10. 특정 주소에서만 공통으로 보여지는 layout 만들기

예를 들어, 아래와 같이 {베이스주소}/flowers 로 접근할 때만 공통으로 보여지는 헤더와 푸터를 만들어 볼 수도 있다

image-20230906113943758

image-20230906113948049


image-20230906114046239


순서 1. flowers 하위 경로에 layout.tsx를 아래와 같이 만든다. 이 때, 주목할 내용은 app 하위 경로에 있을 때와 달리, 태그(html, body)를 붙일 필요가 없다는 것이다

// app > flowers > layout.tsx

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

export default function RootLayout({
    children,
}: {
    children: React.ReactNode
}) {
    return (
        <>
            <header className={styles.header}>나는 flower 페이지의 헤더다</header>
            <main>{children}</main>
            <footer className={styles.footer}>나는 flower 페이지의 푸터다</footer>
        </>
    )
}

순서 2. 위와 동일한 경로에 css 파일을 생성한다

// app > flowsers > layout.module.css

.header {
    color: orange;
}

.footer {
    color: green;
}

NextJS에서는 특정 주소나 외부 주소로 이동 시, Link 태그를 사용해야 한다. 예를 들어, {베이스주소}/flowers 페이지에 특정 페이지로 이동할 수 있는 두 개의 링크를 만들어보겠다

순서 1. flowsers 경로에 page.tsx 파일을 생성하고, 아래와 같이 Link 태그를 사용하는 코드를 추가한다

// app > flowers > page.tsx

import Link from "next/link"; // 임포트

export default function flowers() {
    return (
        <>
            {/* 1. 해당 링크로 이동 */}
            <Link href='/flowers/lily'>릴리로 이동, </Link>
            <Link href='/'>홈으로 이동</Link>
        </>
    );
}

1번. Link 태그는 NextJS에서 기본으로 제공한다. 태그를 사용하지 않음을 참고해두자


순서 2. 테스트

image-20230906120702265

image-20230906120707701


12. SEO와 Metadata

먼저, SEO는 검색 엔진 최적화(Search Engine Optmization) 검색엔진(google, naver 등)이 자료를 수집하고 순위를 매기는 방식에 맞게 웹 페이지를 구성해서 검색 결과의 상위에 나올 수 있게 한다. (참조: 위키백과)

Next.js Metadata API 를 갖고 있는데, 이 기능으로 향상된 SEO를 위한 메타데이터를 작성할 수 있다. (참조: Next.js 공식문서)

12-1. Metadata를 전체 페이지에 적용하기

순서 1. 전체 페이지의 Metadata를 살펴봐야하기 때문에, app > page.tsx 파일에 작성해준다

// src > app > page.tsx

import { Metadata } from "next"; // 1. 임포트

export const metadata: Metadata = { // 2. 메타데이터
  title: "넥스트 테스트페이지",
  description: "넥스트 테스트페이지 입니다."
}

export default function Home() {
  return (
    <h1>This is firstPage!</h1>
  )
}

1번. Next.js에서 제공하는 Metadata의 인터페이스를 임포트

2번. Metadata를 자료형으로 metadata의 속성을 객체 형태로 작성


순서 2. 브라우저의 개발자 도구에서 보면, head 태그에 각각 title과 description이 들어간 것을 볼 수 있다. head 태그에 있는 icon(파비콘)도 변경할 수 있다

image-20230906135638607


12-2. 특정 주소로 진입할때마다 Metadata를 변경하고 싶다면?

특정 주소 경로의 page.tsx에 메타데이터를 추가하면 된다.

예를 들어, flowers 페이지에 메타데이터를 등록하고 싶다면, 아래와 같이 app > flowers > page.tsx 파일에 작성하면 된다. 관리자 도구에서 head의 title 부분이 변경된 걸 확인할 수 있다

// app > flowers > page.tsx

... 생략 ...
import { Metadata } from "next";

export const metadata: Metadata = { // 메타데이터 등록
    title: "넥스트 테스트페이지 | 꽃페이지",
}
... 생략 ...

image-20230906140904622


다음에 다루게 될 것

  • Next.js 복습(2): 렌더링

태그:

카테고리:

업데이트:

댓글남기기