[Next.js] Next.js 시작하기: 4편

donguraemi·2023년 8월 25일
0

넥스트JS

목록 보기
5/5
post-thumbnail

Next.js 시작하기: 3편에서 pages 사용 방법에 대해 알아보았다. 이번엔 apppage의 차이에 대해 알아보자.

Next.js 13.4부터는 App 라우터를 안정성 있게 사용할 수 있다.

  • app/layout.js
/*
	The root layout for the entire application
*/
export default function RootLayout({ children }) {
	return (
      <html>
        <body>{children}</body>
      </html>
    );
}

Pages 라우터에서 컴포넌트를 한 겹 감싸기 위해서는 Layout 컴포넌트를 만들고 해당 컴포넌트를 불러와서 사용해야 했다. 하지만 App 라우터는 Layout 컴포넌트를 가져오지 않아도 자동으로 감싸진다. 레이아웃은 중첩하여 사용할 수 있다.
nested layout

  • app/page.js
export default function Page() {
	return (
      <h1>Hello, Next.js!</h1>
    );
}

💥app 경로 vs. page 경로

pages 디렉토리app 디렉토리경로
index.jspage.js/
about.jsabout/page.jsabout
blog/[id].jsblog/[id]/page.js/blog/1

File Conventions

Next.js 파일은 중첩된 경로에서 특정 동작으로 UI를 생성하기 위해 특수한 파일들을 사용한다.

  • layout : Shared UI for a segment and its children
  • page : Unique UI of a route and make routes publicly accessible
  • loading : Loading UI for a segment and its children
  • not-found : Not found UI for a segment and its children
  • error : Error UI for a segment and its children
  • global-error : Global Error UI
  • route : Server-side API endpoint
  • template : Specialized re-rendered Layout UI
  • default : Fallback UI for Parallel Routes

Creating UI

App 라우터는 각 경로의 segment에 대한 UI를 생성하기 위해 특수 파일 규칙을 사용한다. page는 경로에 고유한 UI를 표시하고, layout은 하위 경로에서 공유되는 UI를 표시한다.


Custom App

route

Next.js는 폴더를 사용하여 경로를 정의하는 파일 시스템 기반 라우터를 사용한다. 각 폴더는 URL segment에 매핑된다. 중첩된 경로를 만들려면 폴더를 중첩하면 된다. 특수한 page.js 파일을 사용하면 경로 segment에 공개적으로 접근할 수 있다.

page file
위의 예시에서 /dashboard/analytics 경로는 page.js 파일이 없으므로 접근할 수 없다. 반면, /dashboardpage.js를 하위 파일로 갖고 있기 때문에 접근 가능하다.

  • Create a shared layout between page changes
  • Inject additional data into pages
  • Add global CSS

From Pages to App

  • Update Next.js application from version 12 to version 13
  • Upgrade features that work in both the pages and the app directories
  • Migrate existing application from pages to app

Upgrading New Features

  • <Image /> 컴포넌트 (next/image)
  • <Link /> 컴포넌트 (next/link)
  • <Script /> 컴포넌트 (next/script)

Migrating from page to app

  • app 디렉토리는 중첩된 경로와 레이아웃을 지원한다.
  • 경로 segment를 위한 UI를 만들기 위해 특수한 file convention을 이용한다.
    • page.js : define UI unique to a route
    • layout.js : define UI that is shared across multiple routes
  • getServerSidePropsgetStaticProps 같은 기존 데이터 fetching 함수들은 app의 새로운 api로 대체된다.
  • pages/_app.jspages/_document.jsapp/layout.jsRootLayout으로 대체된다.
  • pages/_error.jserror.js, pages/404.jsnot-found.js로 대체된다.
  1. Creating Root Layout
/* 
	app/layout.js
    app 내부의 모든 경로에 적용되는 루트 레이아웃
*/
export default function RootLayout({ chidren }) {
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}
  • app 디렉토리는 RootLayout을 포함해야 한다.
  • RootLayout<html><body> 태그를 정의해야 한다.
  1. Migrating next/head

pages 디렉토리에서는 next/head의 리액트 컴포넌트를 사용하여 <title>이나 <meta> 같은 HTML 요소들을 포함하는 <head>를 관리했다. app 디렉토리에서는 built-in SEO로 대체되어 사용된다.

import { Metadata } from 'next';

export const metadata = {
  title: 'My Page Title'
}

export default function Page() {
  return ...
}

Template

템플릿은 하위 레이아웃이나 페이지를 감싸는 레이아웃과 유사하다. 하지만 경로 전반에 걸쳐 지속되고 상태롤 유지하는 레이아웃과 달리, 템플릿은 각 하위 항목에 대해 새로운 이스턴스를 만든다. 즉, 템플릿을 공유하는 경로 사이를 왔다갔다할 때, 컴포넌트의 새 인스턴스가 마운트 되고, DOM 요소가 다시 생성되며, 상태가 유지되지 않고 효과가 다시 동기화 된다. 템플릿은 다음 사례에서 유용하게 쓰인다.

  • useEffectuseState에 의존할 때
  • 기본 프레임워크 동작을 변경할 때
export default function Template({ children }) {
	return <div>{children}</div>
}

Client Component vs. Server Component

서버 및 클라이언트 컴포넌트를 사용하면 클라이턴트 측의 풍부한 상호작용과 기존 서버 렌더링의 향상된 성능을 결합하여 서버와 클라이언트를 포괄하는 어플리케이션을 만들 수 있다.

  • Server Component

서버 컴포넌트는 서버와 클라이언트를 아우르는 하이브리드 어플리케이션을 만들기 위한 모델이다. SPA처럼 리액트가 전체 어플리케이션을 클라이언트 측에서 렌더링 하는 대신, 목적에 따라 렌더링 위치를 선택할 수 있는 유연성을 제공한다.

example
위와 같은 페이지가 있다고 가정해보자. 페이지를 작은 컴포넌트들로 나누었을 때, 대다수의 컴포넌트들이 상호작용을 필요로 하지 않고 서버 컴포넌트로 렌더링이 가능하다는 것을 알 수 있다. 서버 컴포넌트를 이용하면 클라이언트 컴포넌트에 비해 어떤 이점이 있을까?

서버 컴포넌트를 이용하면 서버 인프라를 효과적으로 활용할 수 있다. 예를 들어, 이전에 클라이언트 자바스크립트 번들 크기에 영향을 미쳤던 large dependencies가 서버에 대신 남기 대문에 성능을 향상 시킬 수 있다. 이처럼 서버 컴포넌트는 리액트 어플리케이션을 PHP, Ruby on Rails와 유사하게 느끼도록 하지만 리액트의 강력함과 유연성도 사용할 수 있다.

또한 서버 컴포넌트를 사용하면 초기 페이지 로드가 빨라지며 클라이언트 측의 자바스크립트 번들 크기가 줄어든다.

  • Client Component

클라이언트 구성 요소를 사용하면 어플리케이션에 상호작용을 추가할 수 있다. 클라이언트 컴포넌트는 페이지 라우터 방식으로 볼 수 있다.

use client
use client

💥 언제 서버/클라이언트 컴포넌트를 사용해야 할까

클라어언트 컴포넌트 사용 사례가 있을 때까지 서버 컴포넌트를 사용하는 게 좋다.

클라이언트 컴포넌트 사용 사례

  • 상호작용 및 이벤트 : onClick(), onChange()
  • 상태 및 수명 주기 사용 : useState(), useReducer(), useEffect()
  • 브라우저 전용 API 사용

Linking and Navigating

Next.js에서 경로를 탐색하는 방법은 2가지다.
1. <Link> 컴포넌트 사용하기
2. useRouter 훅 사용하기


<Link><a> HTML 태그를 확장하여 경로 간 prefetch와 클라이언트 측 탐색을 제공하는 내장 컴포넌트다. next/link에서 가져와 사용하고, href 값을 전달한다.

/*
	Linking to dynamic routes
*/
import Link from 'next/link'

export default function PostList({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/posts/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}

💥 스크롤 위치 조작하기
Next.js의 App 라우터는 기본적으로 새 경로는 스크롤을 맨 위로 올리고, 앞뒤 탐색은 스크롤 위치를 유지한다. 특정 항목으로 스크롤하려면 url에 #을 추가하여 항목의 위치로 이동할 수 있다.

<Link href='/dashboard#settings'>Settings</Link>

Next.js의 기본 동작을 비활성화 하고 싶다면 scroll={flase}를 전달하면 된다.

useRouter를 사용하는 경우

import { useRouter } from 'next/navigation';
router.push('/dashboard', {scroll: false});

useRouter Hook

useRouter를 사용하면 프로그래밍 방식으로 경로를 변경할 수 있다. useRouter는 클라이언트 컴포넌트 내부에서 사용되며 next/navigation에서 가져와야 한다.

'use client'
import { useRouter } from 'next/navigation';

export default function Page() {
  const router = useRouter();
  return (
    <button type='button' onClick={() => router.push('/dashboard')}>
      Dashboard
    </button>
  );
}

How Routing and Navigation Works

App 라우터는 하이브리드 접근 방식을 사용한다. 서버에서 어플리케이션 코드는 경로 segment별로 자동으로 코드 분할이 이루어진다. 그리고 클라이언트에서는 경로 segment를 prefetch하고 캐시한다.

사용자가 새 경로로 이동하면 브라우저는 페이지를 다시 로드하지 않고 변경된 경로 segment만 다시 렌더링하여 탐색 경험과 성능을 향상시킨다.

  1. Prefetch

사용자가 방문하기 전에 백그라운드에서 경로를 미리 불러오는 것을 의미한다. Next.js에서 경로를 prefetch하는 방법은 두 가지다.

  • <Link> 컴포넌트 (경로가 사용자의 뷰포트에 표시되면 자동으로 prefetch)
  • useRouterrouter.prefetch()
  1. Caching

Next.js는 라우터 캐시라 불리는 클라이언트 측 메모리 캐시가 존재한다. 사용자가 어플리케이션을 탐색할 때, 미리 가져온 경로 segment와 방문한 경로들이 저장된다. 이는 탐색 시 서버에 새로운 요청을 보내는 대신 캐시를 최대한 재사용하여 성능을 향상시킨다는 의미다.

  1. Partial Rendering

부분 렌더링은 바뀐 경로의 segment만 렌더링하는 것을 의미한다. 예를 들어 /dashboard/settings/dashboard/analytics 사이를 탐색한다고 가정하자. settingsanalytics는 렌더링되지만 서로 공유하는 dashboard 레이아웃은 보존된다.
partial rendering

  1. Soft Navigation

브라우저는 기본적으로 페이지를 다시 로드하고 어플리케이션의 useState 같은 상태를 초기화하며 사용자의 스크롤 위치 같은 브라우저 상태도 초기화한다. 하지만 Next.js의 App 라우터는 부드러운 탐색을 지원한다. 바뀐 부분만 렌더링하고 리액트와 브라우저 상태를 유지한다.

  1. Back and Forward Navigation

Next.js는 앞으로/뒤로 가기를 수행해도 스크롤의 위치를 유지하고 라우터 캐시의 경로 segment를 재사용한다.


Route Group

app 디렉토리의 하위 경로는 url 경로로 매핑된다. 하지만 특정 디렉토리명을 url에서 생략하고 싶은 경우가 생길 수 있다. 이때 경로 그룹을 사용한다. (folderName)
route group

경로 그룹은 특정 세그먼트를 레이아웃으로 선택할 수 있다. shop의 하위 페이지인 accountcart는 레이아웃을 공유해야 한다고 가정하자. shop을 경로 그룹으로 변경하고 (shop), accountcart를 하위 디렉토리로 생성하고 그 밑에 page.js를 만든다. accountcart는 파일 컨벤션에 의해 상위 레이아웃이 적용된다.
route group to layout


generateStaticParams

generateStaticParams는 동적 경로와 SSG를 함께 사용하고 싶을 때 사용한다.

Pages 라우터에서는 getStaticPathsgetStaticProps를 통해 SSG를 구현했다. generateStaticParams는 Pages 라우터의 getStaticPaths를 대체하는 함수다.

export async function generateStaticParams() {
  const posts = await fetch('https://.../posts').then(res => res.json());
  return posts.map(post => ({
    id: post.id
  }));
}

export default function Post({ params }) { ... }
경로generateStaticParams 반환 타입
/product/[id]{ id: string }[]
/products/[category]/[product]{ category: string, product: string }[]

Route Handlers

경로 핸들러는 웹 요청과 응답 api를 이용하여 사용자 경로를 만들 수 있다. 경로 핸들러는 app 디렉토리 내의 route.js|ts 파일에 정의된다. 경로 핸들러는 app 디렉토리에서 중첩될 수 있다.

경로 핸들러는 오직 app 디렉토리 내에서만 사용 가능하다.

route handler

/*
	app/api/route.js
*/
import { NextResponse } from 'next/server';

export async function GET() {
  const res = await fetch('https://...', {
    headers: {
      'Content-Type': 'application/json',
    }
  });
  const data = await res.json();
  return NextResponse.json({data});
}

NextResponse

import { NextResponse } from 'next/server';
  • json() : 주어진 JSON body로 응답 생성
export async function GET(request) {
  return NextResponse.json({ error: 'Internal Server Error'}, {status: 500});
}
  • redirect()
const loginUrl = new URL('/login', request.url);
return NextReponse.redirect(loginURL);
  • cookies
response.cookies.set('show-banner', 'false');

참고자료
NEXT.js : app router migration
NEXT.js : defining routes
NEXT.js : route handlers
NEXT.js : nextResponse

0개의 댓글