Next 15

Layout

Route Group

  • 이름이 소괄호로 감싸진 폴더

  • 경로상에 아무런 영향을 끼치지 않음

  • 동시에 각기 다른 경로를 갖는 페이지 파일들을 하나의 폴더로 묶을 수 있는 기능

  • 경로가 다른 특정 페이지들에 동일한 레이아웃을 사용하고 싶을 때 사용됨

서버 컴포넌트

서버 측 사전 렌더링에서 딱 한번 실행되는 컴포넌트

반대로 클라이언트 컴포넌트는, 1. 서버 측 사전 렌더링, 2. 하이드레이션 총 2번 실행된다.

주의사항

  • 서버 컴포넌트는 브라우저에서 실행될 코드가 포함되면 안된다.

    • 리엑트 훅, 이벤트 핸들러, 브라우저에서 실행되는 기능을 담은 라이브러리

    • Link 컴포넌트는 서버 컴포넌트에서 사용 가능 (서버에서는 정적인 <a> 태그로 렌더링되고 이동 자체는 요소가 가진 기능이기 때문)

  • 클라이언트 컴포넌트는 클라이언트에서만 실행되지 않는다.

  • 클라이언트 컴포넌트에서 서버 컴포넌트를 import 할 수 없다.

    • 하이드레이션 진행 중에 서버컴포넌트는 포함되지 않기 때문에 문제 발생

    • 이 경우 next.js는 자동으로 서버 컴포넌트를 클라이언트 컴포넌트로 변경 (왼만하면 피하기)

    • 클라이언트 컴포넌트에서 children props를 사용하여 서버 컴포넌트를 자식으로 사용하는 방법은 OK

  • 서버 컴포넌트에서 클라이언트 컴포넌트에게 직렬화되지 않는 props는 전달할 수 없다.

    • JSON으로 직렬화할 수 없는 데이터 -> 함수, 클래스, Map/Set,

    • 그 외 직렬화 가능한 데이터

      • string, number, boolean, null, undefined

      • array, object

RSC Payload

React Server Component의 순수 데이터(결과물)

React Server Component를 직렬화한 결과

  • 사전 렌더링 과정 중에는 서버 컴포넌트들만 따로 먼저 실행하여 RSC Payload 형태로 직렬화되는 과정 존재

  • 서버 컴포넌트들만 따로 실행시키게 되면 그 결과로 HTML 태그가 바로 실행되는게 아닌 RSC Payload라는 JSON과 비슷한 문자열이 생성됨

  • RSC Payload는 서버 컴포넌트의 모든 데이터가 포함됨

    • 렌더링 결과

    • 연결된 클라이언트 컴포넌트 위치

    • 클라이언트 컴포넌트에게 전달하는 props 값

네비게이팅

Page Routing과 동일하게 페이지 이동은 Client Side Rendering 방식으로 처리 단, 서버 컴포넌트 개념이 추가되어 방식이 조금 변경됨

  • 페이지 이동 요청 후 JS 번들과 같이 서버 컴포넌트 결과물인 RSC Payload도 함께 전달

  • 프로그래밍 방식으로 페이지 이동은 useRouter (next/navigation) 사용 *클라이언트 컴포넌트에서만

Prefetching

현재 페이지에서 링크들이 존재하여 연결된 페이지의 데이터들을 미리 불러오는 기법

  • 연결된 페이지가 정적 페이지(SSG)인 경우 RSC Payload + JS Bunlde 프리패칭하지만 동적 페이지(SSR)인 경우 RSC Payload만 가져옴

    • 기본적으로 SSG (데이터가 향후 업데이트 되지 않을것이므로 JS Bundle 까지 프리패칭)

    • 빌드 타임에서 가져오기 어려운 경우(예: 쿼리 파라미터 사용 등)은 모두 동적 페이지로 분류

데이터 패칭

  • 서버 컴포넌트async 를 붙여서 데이터 패칭 가능

  • 기존 getStaticProps, getServerSideProps를 대체

    • 더이상 페이지 컴포넌트로부터 서버에서 패칭한 데이터를 넘겨 받지 않아도됨

    • "Fetching data where it's needed"

환경 변수에서 NEXT_PUBLIC_ 접두사를 제거하면 서버에서만 읽을 수 있어 private하게 동작함

데이터 캐시

fetch 메서드를 통해 불러온 데이터를 Next 서버에 보관하는 기능

  • 영구적으로 보관하거나, 특정 시간 주기로 갱신 가능

  • 불필요한 데이터 요청 수를 줄여서 성능 개선

  • fetch 전용

    • fetch('endpoint', { cache: 'force-cache' })

    • 일반 fetch가 아닌 next 전용 메서드임

  • force-cache: 요청 결과를 무조건 캐싱, 한번 호출된 후 다시 호출되지 않음

  • (default) no-store: 요청 결과를 캐싱하지 않음, 캐시를 절대하면 안되는 리소스에 사용

    • next15에서 변경됨 14에서는 기본으로 캐싱

  • revalidate: 특정 주기마다 캐시를 갱신 (sec)

    • 페이지 라우팅 방식에 ISR과 유사

    • 만료 된 후의 첫 요청은 캐시된 데이터를 보여주고 갱신, 다음 요청 부터 갱신된 데이터 반환

  • tags: 특정 태그 기반으로 캐시를 무효화할 수 있도록 설정

    • 온디멘드 리벨리데이트

    • 요청이 들어왔을 때 최신 데이터 갱신

cache가 되었는지 로깅 방법

// next.config.js
const nextConfig = {
  logging: {
    fetches: {
      fullUrl: true
    }
  }
}

리퀘스트 메모이제이션

여러 서버 컴포넌트에서 중복된 요청들을 캐싱해서 한 번만 요청하도록 최적화

  • 데이터 캐시와는 다름

  • 하나의 페이지를 렌더링하는 동안 중복된 요청들에 한해서 캐싱하기 위해 사용

  • 렌더링이 종료되면 캐시 소멸

풀 라우트 캐시

Next 서버측에서 빌드 타임에 특정 페이지의 렌더링 결과를 캐싱하는 기능

  • SSG와 유사한 방식

  • 다이나믹 페이지로 생성되는 페이지 외 모두 정적 페이지로 생성됨 (Default)

    • 동적 함수를 사용하지 않고 데이터 캐시를 사용하는 경우

다이나믹 페이지로 설정되는 기준

서버 컴포넌트만 해당, 클라이언트 컴포넌트는 페이지 유형에 영향을 미치지 않음

  • 특정 페이지가 접속 요청을 받을 때 마다매번 변화가 생기거나, 데이터가 달라지는 경우

  • 캐시되지 않는 Data Fetching을 사용할 경우

    • 해당 컴포넌트를 사용하는 모든 컴포넌트들은 자동으로 다이나믹 페이지로 설정된다.

// 캐시 옵션 없이 fetch를 사용하는 경우
async function Comp() {
  const response = await fetch("..."); // or fetch("...", { cache: "no-store" })
  return <div>...</div>
}
  • 동적 함수 (쿠키, 헤더, 쿼리스트링) 을 사용하는 컴포넌트가 있을 때

    • 요청에 따라 달라질 수 있는 값들

import { cookies, headers } from 'next/headers'

async function Comp() {
  const cookieStore = cookies();
  const theme = cookieStore.get('theme');
  
  const headersList = headers();
  const authorization = headersList.get('authorization');
  
  return <div>...</div>
}

async function Page({ searchParams }: { searchParams: { q: string } }) {
  const q = searchparams.q;
  //...
}

Revalidate

페이지 라우트의 ISR 방식과 유사

  • 풀 라우트 캐시로 동작하는 페이지 내부에서 revalidate 옵션이 적용된 데이터 패칭이 존재한다면

  • tanstack-query와 비슷, reavlidate time이 경과했다면 우선 풀라우트 캐싱된 페이지를 보여주고 데이터 캐시를 업데이트 후 풀 라우트 캐시 업데이트, 이후 요청엔 업데이트 된 페이지 반환

Suspense

  • useSearchParams 훅은 현재 요청받은 페이지의 쿼리스트링을 조회하는 역할

  • 쿼리스트링은 빌드타임에 존재할 수 없음

  • 빌드타임에 클라이언트 컴포넌트를 실행할 때 빌드 타임에 알 수 없는 쿼리스트링 같은 값을 사용하는 훅을 만나면 에러를 발생

    • 클라이언트측에서만 실행되도록, 즉 사전 렌더링에 배제되도록 설정해야함

    • 해당 클라이언트 컴포넌트를 <Suspense> 로 감싸면 됨

    • 넥스트 서버는 사전 렌더링할 때 <Suspense>를 만나면 곧바로 렌더링하지 않고 Fallback 컴포넌트를 렌더링함

  • 쿼리스트링을 불러오는 useSearcmParams 는 비동기로 동작함

    • 쿼리스트링을 불러와야 비동기가 종료됨

generateStaticParams

동적 경로를 갖는 페이지를 정적 페이지로서 빌드 타임에 생성되도록 하는 방법

  • 빌드타임에 넥스트서버가 페이지에 어떤 경로(파라미터)가 존재할 수 있는지 정의하여 리턴

    • 미리 정의하지 않은 (파라미터)페이지에 대해서도 동일하게 모두 정적 페이지로 설정됨 (다만 동작이 조금 다름)

    • 미리 정의하지 않은 파라미터의 경우는 서버에 요청이 들어왔을 때 실시간으로 넥스트 서버에 생성되어 이후 요청에 풀 라우트 캐싱이 적용됨

    • 넥스트에서는 동적 경로를 갖는 페이지에 한해 예외적으로 generateStaticParams 함수가 존재한다면, 무조건 정적 페이지로 설정하게됨

  • 파라미터 값은 문자열로만 작성

  • generateStaticParams 를 사용하게 되면 페이지 내부에 데이터 캐싱이 적용되지 않은 데이터 패칭이 있더라도 무조건 해당 페이지는 강제로 정적페이지로 생성

  • 페이지 라우터의 getStaticPaths 와 동일

export function generateStaticParams() {
  return [{ id: "1" }, { id: "2" }, { id: "3" }] // url 파라미터를 담은 배열을 반환
}

라우트 세그먼트 옵션

페이지 내부에서 사용되는 컴포넌트들의 데이터 패칭 캐싱, 리벨리데이트 타임을 강제로 설정

즉, 특정 페이지를 강제로 정적 페이지 또는 동적 페이지로 설정하는 방법

  • dynamicParams와 같이 약속된 변수를 선언 후 값을 설정하여 반환하는 페이지 설정을 라우트 세그먼트 옵션이라함

dynamic

특정 페이지의 유형을 강제로 static, dynamic 페이지로 설정

export const dynamic = 'auto' | 'force-dynamic' | 'force-static' | 'error'  
  • auto [default]: 아무것도 강제하지 않음

  • force-dyanmic: 페이지를 강제로 동적 페이지로 설정

  • force-static: 페이지를 강제로 정적 페이지로 설정

    • 동적 함수를 사용하는 페이지에서 문제 발생, 빈값으로 들어옴

  • error: 페이지를 강제로 정적 페이지로 설정

    • 정적 페이지로 설정하면 안되는 이유(동적 함수, 데이터 패칭 캐시 등)은 빌드 오류를 발생시킴

클라이언트 라우트 캐시

브라우저에 저장되는 캐시

  • 페이지 이동을 효율적으로 하기 위해 RSC Payload에서 추출한 페이지 일부 데이터(레이아웃)를 보관함

  • 여러 페이지가 공통된 레이아웃을 사용하는 경우 중복된 데이터를 계속해서 전달받게 되는 문제 발생

  • 이런 비효율을 줄이기 위해 넥스트는 클라이언트 라우터 캐시 라는 새로운 캐시 공간을 마련해서 해당 데이터들을 자동 보관

  • 기본적으로 새로고침시 모두 비워지게됨

스트리밍이란

*스트림[(액체·기체가) 줄줄[계속] 흐르다[흘러나오다] )], 잘게 쪼개진 데이터들을 연속적으로 보내주는 기술

  • 클라이언트 입장에서 모든 데이터를 전달받지 않은 상태에서도 현재까지 받은 데이터 일부에 접근 가능

  • 화면 렌더링 시, 블로킹 없이 즉시 보여줄 수 있는 부분은 먼저 빠르게 렌더링하고, 데이터 패칭이 필요한 영역은 우선 폴백 UI를 보여준 뒤 데이터를 점진적으로 받아와 전체 화면이 막힘없이 자연스럽게 그려지도록 하는 방식

  • Next.js Dynamic Page에서 자주 사용됨

loading.tsx

비동기 페이지 컴포넌트에 스트리밍을 적용하는 방법

  • loading.tsx를 활용하면, 페이지가 데이터 패칭으로 인해 지연되는 상황에서도 폴백 UI를 먼저 보여줄 수 있음

  • async 가 붙은 비동기 페이지 컴포넌트를 스트리밍 하도록 설정

  • 동일 경로의 페이지 컴포넌트만 스트리밍되는 것이 아니라, 레이아웃 컴포넌트처럼 해당 경로 내부에 포함된 모든 비동기 페이지 컴포넌트들까지 스트리밍되도록 설정됨

  • 쿼리스트링이 변경되는 경우는 트리거되지 않음

    • 이 경우에도 스트리밍을 적용시키고 싶다면 Suspense 사용

Suspense

컴포넌트별로 세밀하게 스트리밍을 적용하는 방법

  • Suspense(미완성), 스트리밍할 컴포넌트를 감싸 해당 컴포넌트가 완전히 준비될 때까지 폴백 UI를 먼저 렌더링해주는 역할

  • 쿼리스트링이 변경되더라도 기본적으로는 Suspense가 다시 트리거되지 않지만, key props를 활용하면 특정 값이 변경될 때마다 로딩 상태로 다시 돌아가도록 설정 가능

  • 병렬로 하나의 페이지에 여러 컴포넌트들을 완료되는 순서대로 각각 렌더링 가능

스켈레톤 UI

뼈대 역할을 하는 UI

  • 로딩 시간에 대략 어떻게 생긴 컨텐츠가 나올지 미리 예측 가능

  • 레이아웃 쉬프트 방지

error.tsx

에러핸들링

'use client'

export default function Error ({ error, reset } : { error: Error, rest: () => void}) {
  const router = useRouter();
  
  return (
    <div>
      <h3>Error 발생</h3>
      <button 
        type="button" 
        onClick={() => {
          startTransition(() => {
            router.refresh()
            reset();
          })
      }}>리셋<button/>
    </div>
  )
}
  • use client 키워드로 클라이언트 컴포넌트로 설정

    • 에러는 서버/클라이언트 막론하고 발생하기 때문에 서버와 클라이언트 모두 렌더링되는 클라이언트 컴포넌트로 생성

  • error.tsx는 현재 자신의 경로에 해당하는 layout까지만 렌더링

  • 같은 경로 또는 하위 경로에 에러가 발생했을때 페이지 컴포넌트 대신 해당 에러 컴포넌트 렌더링

  • 에러 컴포넌트는 에러 정보를 포함하는 error 데이터, error를 리셋시켜주는 함수를 props로 제공한다.

    • error: 자바스크립트 에러 객체로, 어떤 에러가 발생했는지에 대한 정보를 담고 있음

    • reset: 에러 상태를 초기화하고 컴포넌트를 다시 렌더링시키는 함수

      • reset은 클라이언트 측에서만 동작하며, 서버 컴포넌트를 다시 실행하거나 서버 데이터를 refetch 하지는 않음. 즉, 클라이언트에서 발생한 오류만 리셋 가능함

      • Next.js에서 제공하는 useRouterrefresh 메서드를 사용해야 서버 컴포넌트를 다시 실행할 수 있음. 이 refresh 함수는 현재 페이지에서 필요한 서버 컴포넌트를 Next 서버 측에 재요청하도록 트리거함 -> refersh로 서버 클라이언트를 다시 받은 후에 reset이 진행되어야함

서버 액션

브라우저에서 호출할 수 있는 서버에서 실행되는 비동기 함수

export default function Page() {
  const createPostAction = async(formData: FormData) => {
    'use server';
    ...
    // formData.get('name') -> FormDataEntryValue | null Type
    const name = formData.get('name')?.toString(); // string | undefined
  }
  
  return (
    <form action={createPostAction}>
      ...
    </form>
  )
}

  • use server -> 넥스트 서버에서만 실행되는 서버 액션 지시자

  • 서버에서만 실행되는 함수를 브라우저에서 호출, 데이터를 FormData 형식으로 전달

  • 기존엔 api로 서버와 통신해야했다면 이제 자바스크립트 함수 하나만으로 서버와 통신 가능해짐

  • 간단히 서버 함수 하나로 api 역할을 대체 가능

  • 액션들은 별도의 폴더(actions)로 분류해서 모듈화하면 깔끔

  • 액션에 필요한 데이터들은 모두 FormData로 입력받도록 설정

재검증

revalidatePath

페이지 재검증 요청

  • 서버 액션 내부에서 호출하면 넥스트서버에게 해당 페이지를 다시 생성하도록 요청해서 서버 액션의 결과를 화면에 바로 나타나도록가능

  • 서버측에서만 호출 가능

    • 서버 컴포넌트

    • 서버 액션

// #1, 해당 주소의 페이지만 재검증
revalidatePath('/page/1')

// #2, 특정 경로의 모든 동적 페이지 재검증
revalidatePath('/page/[id]', 'page')

// #3, 특정 레이아웃을 갖는 모든 페이지 재검증
revalidatePath('/(with-layout)', 'layout')

// #4, 모든 데이터 재검증 (루트 레이아웃을 갖는 모든 페이지들 전부 다 재검증 -> 모든 페이지 재검증)
revalidatePath('/', 'layout');

// #5, 태그 기준, 데이터 캐시 재검증
revalidateTag('tag')

Tag란?

  • 데이터 패칭에 특정 태그를 붙일 수 있는 옵션

  • 해당 태그를 통해 데이터를 초기화 한다거나 재검증함

  • tanstackQuery의 queryKey 와 비슷?

useActionState

const { state, formAction, isPending } = useActionState(액션, 상태 초기값)

고급 라우팅 패턴

페러렐(병렬) 라우트

한 화면의 여러 페이지 컴포넌트(app/하위의 포함된 컴포넌트들)를 병렬로 렌더링시켜주는 패턴

  • 접두사 @ 로 시작하는 폴더를 슬롯이라 한다.

    • 슬롯: 병렬로 렌더링 될 페이지 컴포넌트를 보관하는 폴더

    • URL 경로에는 영향을 끼치지 않음

    • 슬롯의 갯수 제한은 없다.

  • 부모 레이아웃 컴포넌트의 props@abc/page.tsx컴포넌트가 자동으로 전달된다. (-> props abc)

  • 슬롯 폴더 하위에 새로운 경로 페이지를 추가하고 해당 경로로 이동하면, 레이아웃 기준으로 해당 슬롯 영역만 변경됨

    • @abc/other/page.tsx

    • 레이아웃의 나머지 슬롯이 해당 경로와 매칭되는 경로가 없다면 기존 슬롯은 그대로 유지함,

    • 각각의 슬롯들이 이전 페이지를 유지하는건 오직 링크 컴포넌트를 이용하여 브라우저에서 CSR로 접근할때만 유효

      • 초기접속은 슬롯의 이전페이지를 모르기때문

      • 방지하려면 슬롯별로 현재 렌더링할 페이지가 없을 때 대신 렌더링할 페이지 default.tsx 를 생성

사용자가 특정 경로에 접속해서 페이지 요청할 때 특정 조건일 떄 가로채서 다른 페이지를 대신 렌더링하도록 설정하는 패턴

  • 조건은 only 초기접속

    • 조건은 고정 (설정 불가능)

  • (.)폴더명

    • 접두사 (.) 는 가로챌 페이지 폴더의 상대 경로를 의미

    • (.) : 동일 경로

    • (..): 상위 경로 (1dep)

    • (..)(..) : 상위 경로 (2dep)

    • (...) : app/ 바로 하위 경로

<Image /> Next.js에서 제공하는 이미지 컴포넌트 ('next/image')

  • webp 포맷으로 자동 변환

  • 디바이스 사이즈에 맞는 이미지 최적화

  • 레이지 로딩 적용

  • 불러 이미지 활용

  • svg, gif는 따로 최적화 해주지 않음

SEO 최적화

metadata (정적)

export const metadata: Metadata = {
  title: '',
  description: '',
  openGraph: {
    title: '',
    description: '',
    images: [퍼블릭 경로,],
  },
}
  • 메타데이터 변수에 설정된 값이 자동으로 index 페이지의 metadata로 설정됨

  • openGraph를 설정하면 og, twitter 자동 추가됨

generateMetadata (동적)

export async function generateMetadata({ 
  searchParams }: { 
    searchParams: Promise<{ q?: string}> 
}): Promise<Metadata> {
  const { q } = await searchParams;
  
  return {
    ... 
  }
}
  • 현재 페이지 메타데이터를 동적으로 생성

  • 페이지 컴포넌트가 전달받는 매개변수를 동일하게 인자로 받음

Last updated