Next 15
Layout
Route Group
이름이 소괄호로 감싸진 폴더
경로상에 아무런 영향을 끼치지 않음
동시에 각기 다른 경로를 갖는 페이지 파일들을 하나의 폴더로 묶을 수 있는 기능
경로가 다른 특정 페이지들에 동일한 레이아웃을 사용하고 싶을 때 사용됨
서버 컴포넌트
서버 측 사전 렌더링에서 딱 한번 실행되는 컴포넌트
주의사항
서버 컴포넌트는 브라우저에서 실행될 코드가 포함되면 안된다.
리엑트 훅, 이벤트 핸들러, 브라우저에서 실행되는 기능을 담은 라이브러리
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"
데이터 캐시
fetch 메서드를 통해 불러온 데이터를 Next 서버에 보관하는 기능
영구적으로 보관하거나, 특정 시간 주기로 갱신 가능
불필요한 데이터 요청 수를 줄여서 성능 개선
fetch
전용fetch('endpoint', { cache: 'force-cache' })
일반 fetch가 아닌 next 전용 메서드임
force-cache: 요청 결과를 무조건 캐싱, 한번 호출된 후 다시 호출되지 않음
(default) no-store: 요청 결과를 캐싱하지 않음, 캐시를 절대하면 안되는 리소스에 사용
next15에서 변경됨 14에서는 기본으로 캐싱
revalidate: 특정 주기마다 캐시를 갱신 (sec)
페이지 라우팅 방식에 ISR과 유사
만료 된 후의 첫 요청은 캐시된 데이터를 보여주고 갱신, 다음 요청 부터 갱신된 데이터 반환
tags: 특정 태그 기반으로 캐시를 무효화할 수 있도록 설정
온디멘드 리벨리데이트
요청이 들어왔을 때 최신 데이터 갱신
리퀘스트 메모이제이션
여러 서버 컴포넌트에서 중복된 요청들을 캐싱해서 한 번만 요청하도록 최적화
데이터 캐시와는 다름
하나의 페이지를 렌더링하는 동안 중복된 요청들에 한해서 캐싱하기 위해 사용
렌더링이 종료되면 캐시 소멸
풀 라우트 캐시
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 파라미터를 담은 배열을 반환
}
generateStaticParams
로 미리 정의한 파람 외 다른값으로 들어온 페이지 요청건 모두 404로 보내버리고 싶은 경우
export const dynamicParams = false;
라우트 세그먼트 옵션
페이지 내부에서 사용되는 컴포넌트들의 데이터 패칭 캐싱, 리벨리데이트 타임을 강제로 설정
즉, 특정 페이지를 강제로 정적 페이지 또는 동적 페이지로 설정하는 방법
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에서 제공하는
useRouter
의refresh
메서드를 사용해야 서버 컴포넌트를 다시 실행할 수 있음. 이refresh
함수는 현재 페이지에서 필요한 서버 컴포넌트를 Next 서버 측에 재요청하도록 트리거함 -> refersh로 서버 클라이언트를 다시 받은 후에 reset이 진행되어야함
하지만 refresh
함수의 반환값은 void
이기 때문에 async/await
으로 기다릴 수는 없음.
이런 경우에는 React의 startTransition
을 사용하면 해결 가능
startTransition
비동기 UI 업데이트 작업을 일괄로 처리할 수 있도록 콜백을 감싸서 실행해하면,
서버 데이터를 새로 받아오고 이후 reset
을 호출하는 흐름을 자연스럽게 구성
startTransition
는 작업을 우선순위가 낮은 transition(변화) 단위로 묶어줌,
transition 단위로 묶인 작업들을 일괄적으로 업데이트
즉, refersh(서버데이터 업데이트)와 reset(에러 초기화 & 리렌더링)를 하나의 transition으로 묶어주면 두 함수 결과가 화면에 반영됨
서버 액션
브라우저에서 호출할 수 있는 서버에서 실행되는 비동기 함수
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로 입력받도록 설정
액션에 필요한 데이터들은 모두 FormData로 입력받도록 설정!
실질적으로 <form/> 내부에서 컨트롤하지 않는 데이터들은, 아래처럼 포함시켜 넘겨주자
<input hidden readonly value={데이터} />
서버 액션의 경우 아직 실무에 도입된 사례가 많지 않다!
재검증
revalidatePath
페이지 재검증 요청
서버 액션 내부에서 호출하면 넥스트서버에게 해당 페이지를 다시 생성하도록 요청해서 서버 액션의 결과를 화면에 바로 나타나도록가능
서버측에서만 호출 가능
서버 컴포넌트
서버 액션
해당 페이지의 모든 캐시들을 무효화시킴
풀라우트 캐시조차도 무효화시키지만 새로 생성된 페이지를 풀라우트 캐시에 업데이트해주진 않는다.
이후 진입했을 때는 다이나믹 페이지처럼 실시간으로 페이지를 만들기때문에 비교적 느릴 수 있다.
// #1, 해당 주소의 페이지만 재검증
revalidatePath('/page/1')
// #2, 특정 경로의 모든 동적 페이지 재검증
revalidatePath('/page/[id]', 'page')
// #3, 특정 레이아웃을 갖는 모든 페이지 재검증
revalidatePath('/(with-layout)', 'layout')
// #4, 모든 데이터 재검증 (루트 레이아웃을 갖는 모든 페이지들 전부 다 재검증 -> 모든 페이지 재검증)
revalidatePath('/', 'layout');
// #5, 태그 기준, 데이터 캐시 재검증
revalidateTag('tag')
useActionState
const { state, formAction, isPending } = useActionState(액션, 상태 초기값)
useActionState
를 사용한다면 서버 액션 함수 첫번째 인자에 state가 전달되게됨
고급 라우팅 패턴
페러렐(병렬) 라우트
한 화면의 여러 페이지 컴포넌트(
app/
하위의 포함된 컴포넌트들)를 병렬로 렌더링시켜주는 패턴
접두사
@
로 시작하는 폴더를 슬롯이라 한다.슬롯: 병렬로 렌더링 될 페이지 컴포넌트를 보관하는 폴더
URL 경로에는 영향을 끼치지 않음
슬롯의 갯수 제한은 없다.
부모 레이아웃 컴포넌트의 props로
@abc/page.tsx
컴포넌트가 자동으로 전달된다. (-> props abc)슬롯 폴더 하위에 새로운 경로 페이지를 추가하고 해당 경로로 이동하면, 레이아웃 기준으로 해당 슬롯 영역만 변경됨
@abc/other/page.tsx
레이아웃의 나머지 슬롯이 해당 경로와 매칭되는 경로가 없다면 기존 슬롯은 그대로 유지함,
각각의 슬롯들이 이전 페이지를 유지하는건 오직 링크 컴포넌트를 이용하여 브라우저에서 CSR로 접근할때만 유효
초기접속은 슬롯의 이전페이지를 모르기때문
방지하려면 슬롯별로 현재 렌더링할 페이지가 없을 때 대신 렌더링할 페이지
default.tsx
를 생성
사용자가 특정 경로에 접속해서 페이지 요청할 때 특정 조건일 떄 가로채서 다른 페이지를 대신 렌더링하도록 설정하는 패턴
조건은 only 초기접속
조건은 고정 (설정 불가능)
(.)폴더명
접두사
(.)
는 가로챌 페이지 폴더의 상대 경로를 의미(.)
: 동일 경로(..)
: 상위 경로 (1dep)(..)(..)
: 상위 경로 (2dep)(...)
: app/ 바로 하위 경로
페러럴 라우트와 인터셉팅 라우트를 함께 사용하게 되면 인스타그램 피드처럼 인터셉팅 라우트로 특정 페이지를 가로채 모달 슬롯에 띄우고, 패럴랠 라우트로 배경 페이지(기존 화면)를 함께 유지하는 UI 구현 가능
<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 {
...
}
}
현재 페이지 메타데이터를 동적으로 생성
페이지 컴포넌트가 전달받는 매개변수를 동일하게 인자로 받음
하나의 페이지에서 렌더링 도중 중복된 API 요청을 방지함 -> 리퀘스트 메모제이션 고로 genearateMetadata 내부에서 중복된 API를 호출해도 괜찮
Last updated