반응형
1. 기능구현의 주도권 유무에 따라 나뉨
Next.js = React.js 전용의 웹 개발 "Framework"
React = UI 개발을 위한 JavaScript "Library"
- Library : 기능구현을 원하는 방향으로 사용 가능 (React.js, JQuery 등등), 자유도 ⬆
- Framework: 프레임워크가 제공하는 기능 이용, 허용하는 범위내에서만 추가 도구 사용 가능 (Next.js, Remix 등등), 자유도 ⬇
2. 렌더링
- React: CSR(Client Side Rendering) 초기접속이후의 페이지 이동이 빠름, FCP(초기접속 속도)가 느림,
index.html, JS 번들, 웹사이트에 필요한 전체코드를 모두 초기에 렌더링 > 클라이언트에서 페이지 이동
* FCP(First Contentful Paint) 요청시작 <-> 컨텐츠 렌더링 까지의 시간
- NEXT: 사전렌더링 FCP시점에서 인터렉션(상호작용) 불가,
index.html(빈 HTML)이 아닌 사전에 렌더링(JS 실행)된 html 을 클라이언트에 전달(현재 요청한 페이지의 JSX부분만) > 화면에 렌더링, FCP > 수화(Hydration, JS 번들 실행) > TTI, 상호작용가능 > 프리패칭(모든 페이지에 대한 js 코드를 사전에 불러옴)으로 CSR 방식으로 빠르게 페이지 이동
* next 개발모드에서는 프리패칭 ❌
// 프리패칭
const router = useRouter();
const onClickButton = () => {
router.push("/test"); //replace 뒤로가기 방지 하면서 페이지 이동
};
useEffect(() => {
// Link 태그가 아닌 프로그래메틱하게 이동시키는 경우 프리패칭이 되지 않기 때문에 useEffect 사용
router.prefetch("/test");
}, []);
<Link href={"/"}>index</Link>
<Link href={"/search"} prefetch={false}> // 프리패칭 막기
<button onClick={onClickButton}>/test</button>
3. Page Router
- pages의 폴더 안의 파일명/폴더명을 기반으로 페이지 라우팅을 자동으로 제공, 중첩 라우팅(대괄호를 사용해 동적으로 라우팅) 가능 (book/1234)
* catch all segment - [...id] "..." 을 추가해서 모든 구간(segment)에 대응 가능(book/1/34/464/3) but 디폴트 페이지 ❌
* optional catch all segment - [[...id]] 대괄호 중첩해서 디폴트 페이지도 모두 대응 가능 (book/)
- 다양한 방식의 사전 렌더링
- 서버사이드 렌더링(SSR): 가장 기본적인 사전 렌더링 방식, 요청이 들어올때 마다 사전렌더링 진행
// getServerSideProps 넥스트 함수를 이용해 ssr 자동설정
// 컴포넌트 보다 먼저 실행, 컴포넌트에 필요한 데이터를 불러오는 함수
export const getServerSideProps = () => {
const data = "hello";
// props라는 객체 프로퍼티를 포함하는 단 하나의 객체여야 함
// 서버측에서만 실행 > 브라우저에서 사용 ❌ console/window.location 등등
return {
props: {
data,
},
};
};
// 컴포넌트는 서버,브라우저에서 한번씩 총 2번 실행됨
export default function Home({
data,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
//window.location >> 서버에서 한번 실행되기 때문에 오류 발생 >> useEffet 사용
useEffect(() => {
console.log(window);
}, []);
return (...)
}
- 정적 사이트 생성(SSG): 빌드타임에 미리 페이지를 사전 렌더링, 데이터가 빌드타임에 고정되기때문에 실시간 반영 ❌, path가 지정되어 있지 않은 페이지들은 fallback(false,true,blocking) 옵션을 줘서 SSR 방식으로 사전렌더링 됨
// 빌드 후 프로덕션 창에서 확인 가능
export const getStaticPaths = () => {
return {
paths: [
{ params: { id: "1" } },
{ params: { id: "2" } },
{ params: { id: "3" } },
],
// fallback: false, 404 not found 페이지 반환
// fallback: true, 즉시생성, props 없는 페이지만 미리 반환(데이터가 없는 페이지) >> props만 따로 반환
fallback: "blocking", // SSR처럼 즉시생성 but 로딩시간이 길어질수도 있다(빈화면)
};
};
export const getStaticProps = async (context: GetStaticPropsContext) => {
if (!book) { // 존재하지 않는 페이지
return {
notFound: true,
};
}
return {
props: {
book,
},
};
}
export default function Page({
book,
}: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter();
if (router.isFallback) return "loading..."; // fallback 중일때
if (!book) return "try again"; // error
// 빌드할때 페이지 생성 > 검색어 같은 쿼리스트링은 컴포넌트 내에서 useRouter,useState,useEffect를 사용
return (...)
}
- 증분 정적 재생성(ISR): (Incremental Static Regeneration) SSG 방식으로 생성된 정적 페이지를 일정시간 주기로 다시 생성, 매운 빠른 속도로 응답가능, 최신 데이터 반영가능
* On-Demand ISR - 요청을 받을때 마다 페이지 다시 생성(게시판)
export const getStaticProps = async () => {
return {
props: {...},
revalidate: 3, // 3초 기준으로 재생성
};
};
// 사용자요청에 맞춰 재생성
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
await res.revalidate("/"); //index page를 재생성, 원하는 페이지경로 삽입
return res.json({ revalidate: true });
} catch (err) {
res.status(500).send("Revalidation Failed");
}
}
- 페이지별 레이아웃 설정이 번거롭다.
// 컴포넌트 함수도 객체이기 때문에 매서드 추가가능
Home.getLayout = (page: ReactNode) => {
return <SearchableLayout>{page}</SearchableLayout>;
};
// 컴포넌트 분리
export default function SearchableLayout({
children,
}: {
children: ReactNode;
}) {
return (
{children}
);
}
- 데이터 패칭이 페이지 컴포넌트에 집중됨, 사전렌더링 과정에서 불러온 데이터가 페이지 컴포넌트에만 전달되기 때문에 데이터 전달 과정이 번거러워질수 있음
- 불필요한 컴포넌트들(상호작용이 필요없는 컴포넌트)도 JS Bundle에 포함됨
4. App Router
- app 폴더 밑의 구조를 기반으로 페이지 라우팅이 자동 설정, page.tsx 파일만 페이지 파일로 설정됨
// 중첩라우팅, catch all segment 동일
export default function Page({
params,
//searchParams,
}: {
params: { id: string | string[] };
//searchParams: { q?: string };
}) {
return <div>book/[id] 페이지 {params.id}</div>;
}
- layout > page 순으로 렌더링, RouterGroup은 경로상에는 아무런 영향을 미치지 않는 폴더(폴더이름 소괄호"()" 감싸서 사용)로 레이아웃만 동일하게 적용 가능
ㄴsrc/app
ㄴㅡ(with-searchbar) // 경로에 영향을 미치지 않음
ㄴㅡㅡsearch/page.tsx // url/search 경로 폴더
ㄴㅡㅡlayout.tsx // 인덱스를 감싸는 layout 페이지
ㄴㅡㅡpage.tsx // 기본 인덱스 페이지
ㄴㅡbook/[id]/page.tsx // with-searchbar 폴더내의 레이아웃 적용 ❌
export default function Layout({ children }: { children: ReactNode }) {
return (
<div>
<div> 임시</div>
{children}
</div>
);
}
- React Server Component: 서버측에서 딱 한번만 실행되는 컴포넌트, 상호작용이 필요없는 컴포넌트들을 JS Bundle에서 포함 되지 않게 분리, 기본적으로 서버 컴포넌트로 설정됨, 꼭 필요한 경우에만 클라이언트 컴포넌트 사용 권장
* 클라이언트 컴포넌트는 사전렌더링/하이드레이션 총 2번 렌더링
* 서버 컴포넌트 안에서는 브라우저에서만 사용가능한 react hooks 등은 사용 ❌
* 서버 컴포넌트에서 데이터 호출은 비동기 함수로 async await사용 가능
// 클라이언트 컴포넌트
"use client";
export default function Home() {
console.log("home 컴포넌트 실행"); // 서버/클라이언트 2번 실행
useEffect(() => {}, []);
return <div className={styles.page}>인덱스 페이지</div>;
}
// 서버 컴포넌트
export default async function Page() {
const response = await fetch(...);
return (...);
}
- 데이터 캐시: fetch 메서드를 이용해 api 서버측에서 불러온 데이터를 넥스트 서버에 캐싱해두는 기능, 영구보존 및 특정시간 주기로 갱신 가능
- no-store: 데이터 페칭의 결과를 저장하지 않음, 캐싱을 아예 하지 않도록 설정, 기본값
- force-cache: 요청결과를 무조건 캐싱, 한번 호출후 다시 호출되지 않음
- revalidate: 특정시간을 주기로 캐싱, page router의 ISR방식과 유사
- tags: on-demand revalidate, 요청이 들어왔을때 데이터를 최신화 함
const response = await fetch(`~/api`, { cache: "no-store" });
const response = await fetch(`~/api`, { cache: "force-cache" });
const response = await fetch(`~/api`, { next: { revalidate: 3 } });
const response = await fetch(`~/api`, { next: { tag: ['a'] } });
- 리퀘스트 메모이제이션: 하나의 페이지를 렌더링 하는 동안에 중복된 api 요청을 하나의 요청으로 자동으로 합쳐주는 기능, 자동 데이터 패칭 최적화, 페이지 렌더링이 종료되면 캐시된 데이터들이 소멸
- 라우트 세그먼트 옵션: 특정페이지의 유형을 강제로 static/dynamic 설정, 강제하기 때문에 권장하지 않음
- auto : 기본값, 아무것도 강제하지 않음
- force-dynamic : 페이지를 강제로 Dynamic 페이지로 설정
- force-static : 페이지를 강제로 Static 페이지로 설정, 페이지 내의 동적함수들은 모두 빈값(undefined)로 전달됨
- error : 페이지를 강제로 Static 페이지로 설정 (static하면 않되는 동적함수가 있다면 빌드오류를 반환)
export const dynamic = "auto";
export const dynamic = "force-dynamic";
export const dynamic = "force-static";
export const dynamic = "error";
- 서버액션 재검증: 페이지 자체 다시 렌더링, 풀라우트 캐시 삭제 > 새롭게 생성된 페이지를 다시 풀라우트 캐시에 저장하지 않음
- 특정 주소의 해당하는 페이지만 재검증
revalidatePath(`book/${bookId}`);
- 특정 경로의 모든 동적 페이지를 재검증
revalidatePath("/book/[id]", "page");
- 특정 레이아수을 갖는 모든 페이지 재검증
revalidatePath("/(with-searchbar)]", "layout");
- 모든 데이터 재검증
revalidatePath("/", "layout");
- 태그기준(같은 태그 값을 가지는 태그), 데이터 캐시 재검증
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/review/book/${bookId}`,
{ next: { tags: [`review-${bookId}`] } }
);
revalidatePath(`review-${bookId}`);
반응형