쿼리스트링 사용방법
import { useRouter } from "next/router";
export default function Page() {
const router = useRouter(); // router 객체가 이 변수에 저장됨
return <>search</>;
}
/a/b/c/d/e/f 하려면 (...id).tsx 하면 됨: catch all segment 여러개의 경우 router.query 의 id가 배열로 저장됨 ((...id)).tsx 시에는 ~/ 도 대응함 (index) : optinal catch all segment
찾을수 없거나 없는 페이지 안내멘트는 /pages/404.tsx 를 만들면 된다.
네비게이팅
TAG: CSR 방식으로 페이지를 이동하는 방식이 아닌, 일반적인 SSR 방식으로 페이지 이동, 그러므로 페이지 이동이 느림
스타일링
- inline style 입력 가능
- css 파일을 import 하여 사용 가능
- App Component 가 아닌경우 css 불러오기를 제한함 (클래스 네임 충돌을 방지하기 위함)
- Next.js가 기본적으로 CSS Module
- 기존의 CSS 파일을 모듈처럼 사용할 수 있도록 함
- className이 충돌되지 않도록 unique 이름으로 변환 (파일마다)
- index.module.css 파일 생성
- 사용방법
-
import style from "./index.module.css" export default function Page() { return <h1 className={style.h1}>index</h1>
페이지별 레이아웃 설정방법
// _app.tsx
import GlobalLayout from "@/components/global-layout";
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import { ReactNode } from "react";
import { NextPage } from "next";
type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactNode) => ReactNode; // optional type으로 설정
};
export default function App({ Component, pageProps }: AppProps & { Component: NextPageWithLayout }) {
// getLayout 메소드가 없는 경우에는 page를 반환
const getLayout = Component.getLayout ?? ((page: ReactNode) => page);
return <GlobalLayout>{getLayout(<Component {...pageProps} />)}</GlobalLayout>;
}
// index.tsx
import { ReactNode } from "react";
import SearchableLayout from "@/components/searchableLayout";
export default function Home() {
return <>abcdefg</>
}
Home.getLayout = (page: ReactNode) => {
return <SearchableLayout>{page}</SearchLayout>
}
interface <= 객체타입을 정의하기 위한 TypeScript의 문법임
.className::before {} <= 가상요소
프로그래매틱한 페이지 이동
(사용자가 클릭했을때 페이지를 이동하는 방식이 아닌, 특정 버튼을 눌렀을때 함수가 발생, 혹은 어떠한 이벤트가 발생)
- 예시로, 버튼에 이벤트를 주어 페이지 이동처리함
- 페이지 이동에는 useRouter(next/router) 사용
- useRouter
- replace : history 에 추가하지 않고 페이지 이동 (이전페이지 이력이 없으므로 뒤로가기 불가능)
- push : history 에 추가 후 페이지 이동 (페이지 뒤로가기 했을때 이전 페이지의 이력이 남음)
- prefetch : 클라이언트 전화를 빠르게 하기 위해 페이지를 미리 가져옴
- back : history.back() 과 동일한 기능 (뒤로가기)
- beforePopState : popState를 감지 후 라우터가 처리 전 수행하기 위한 이벤트
- reload : 새로고침
- events : router 내에서 발생하는 다양한 이벤트 감지 기능
프리페칭(prefetch)
-
pre: 사전에 미리
-
fetching: 불러온다
-
next.js에서 초기 접속시 모든 js 번들을 전달하는 것이 아닌, /index인경우 /index에 필요한 번들만 전달, /search 등의 페이지의 번들은 전달하지 않음
-
모든 js 번들을 전달하게 되면 한번의 전달되는 파일의 크기가 커지기 때문에, 다운로드 받는 속도도 브라우저는 느려지기 때문
-
pre-fetching 시, 초기접속이 완료 된 후 페이지 이동 전 미리 js 번들을 받아둠 (pre-fetching)
-
기본적으로 Link 컴포넌트로 명시되지 않은경우 pre-fetch가 안되는데,
useEffect(() => {
router.prefetch("/test");
}, []);
위와 같이 작성시 페이지를 모두 렌더링 후 작성된 페이지를 미리 pre-fetching 함
- 만약 next/link 링크컴포넌트로 작성 후 prefetch를 하지 않고 싶은경우 <Link href={'/link'} prefetch={false} />
API Routes
Next.js에서 API를 구축할 수 있게 해주는 기능
경로
/api/[..path]
React App 에서의 데이터 페칭 (마운트 후 렌더링)
export default function Page() {
const [state, setState] = useState(); // 1. 불러온 데이터를 보관할 State를 생성
const fetchData = async () => { // 2. 데이터 페칭 함수 생성
const response = await fetch("...");
const data = await response.json();
setState(data);
}
useEffect(() => { // 3. 컴포넌트 마운트 시점에 fetchData 호출
fetchData();
}, [])
if(!state) return "Loading..."; // 4. 데이터 로딩중일때의 예외처리
return <div>...</div>;
}
- 컴포넌트 마운트 이후에 발생함
- 데이터 요청 시점이 느려지게 되는 단점 발생
Next.js의 데이터 페칭 (사전 렌더링)
-
사전 렌더링중 발생함 (컴포넌트 마운트 이후에도 발생 가능)
-
데이터 요청 시점이 매우 빨라지는 장점이 있음
-
사전 렌더링이 오래걸릴것 같은 경우 빌드타임을 미리 맞춰두도록 함
-
서버 사이드 렌더링 (SSR: Server Side Rendering)
- 가장 기본적인 사전 렌더링 방식
- 요청이 들어올 때 마다 사전 렌더링을 진행함
-
정적 사이트 생성 (SSG: Static Site Generation)
- SSR의 단점을 해결하는 사전 렌더링 방식
- 빌드 타임에 미리 페이지를 사전 렌더링 해둠
- 단점: 빌드타임 이후에는 최신데이터 반영은 어려움 (똑같은 페이지만 응답)
-
증분 정적 재생성 (ISR)
- 향후에 다룰 사전 렌더링 방식
SSR (서버사이드 렌더링)
export const getServerSideProps = () => {
// 컴포넌트보다 먼저 실행, 컴포넌트에 필요한 데이터 불러오는 함수
// 오직 한번만 실행되는 함수이므로, 처음 렌더링 당시에만 서버에서 실행함.
console.log("서버사이드 Props"); // 이 로그는 브라우저(Client)에서 나오지 않고, 서버측 콘솔에만 보임
// window.location; <= javascript의 window 객체는 클라이언트의 객체를 의미함, client 문법은 오류가 발생함.
const data = "hello";
return {
props: { // 반드시 props 구조가 있어야 함
data
}
}
}
export default function Home({ data }: any) {
// 이 컴포넌트 또한 서버에서 1회 실행 후, 브라우저에서 실행됨
// 첫번째는 사전렌더링을 위해 서버측에서 1회 실행, 브라우저 측
return <>{data}</>
}
-
getServerSideProps 의 역할
- 브라우저에서 해당 페이지로 접속시, page컴포넌트 보다 먼저 실행되어서 해당 컴포넌트에 필요한 데이터를 다른 백엔드 서버에서 불러오는 함수
- 절차
- localhost:3000/index 접속
- ➡️ index.tsx의 getServerSideProps 실행
- ➡️ 최종 페이지 컴포넌트 실행
-
서버컴포넌트 (페이지 컴포넌트) 인경우에는 클라이언트 객체 사용안됨.
-
단, useEffect(() => {}, []) 를 통해 클라이언트 객체를 사용할 수 있음.
-
InferGetServerSidePropsType 타입: getServerSideProps 함수의 반환값 타입을 자동으로 추론해줌. 을 포함하면 자동으로 추론해줌
병렬로 Promise 처리방법
export const getServerSideProps = async () => {
/**
* 기존의 경우 직렬방식, allbooks가 끝나야 recobooks
* const allBooks = await fetchBooks();
* const recoBooks = await fetchRandomBooks();
*/
// 병렬방식으로 변경
const [allBooks, recoBooks] = await Promise.all([fetchBooks(), fetchRandomBooks()]);
return { props: { allBooks, recoBooks } };
};
GetServerSideProps에서 쿼리스트링 받는법
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
return { props: {} };
};
GetStatic 방식으로 페이지 생성하는법 (SSG)
// 기본적으로 getServerSideProps와 동일
export const getStaticProps = async () => {
// const q = context.query.q;
// 빌드타임에는 쿼리스트링을 알 수 없음.
const [allBooks, recoBooks] = await Promise.all([fetchBooks(), fetchRandomBooks()]);
return { props: { allBooks, recoBooks } };
};
export default function Home({ allBooks, recoBooks }: InferGetStaticPropsType<typeof getStaticProps>) {
});
○ (Static) prerendered as static content => SSG와 동일한 정적 페이지, 기본값으로 설정된 페이지 ● (SSG) prerendered as static HTML (uses getStaticProps) ƒ (Dynamic) server-rendered on demand => SSR
SSG 사전 렌더링 사이트 생성
Dynamic한 경로의 경우 getStaticPaths가 필요함
빌드타임(Build Time) [id].tsx 사전렌더링
- 경로 설정이 필요
- id: 1
- id: 2
- id: 3
export const getStaticPaths = () => {
return {
paths: [
{ params: { id: "1" } },
{ params: { id: "2" } },
{ params: { id: "3" } }
],
fallback: false // 없는페이지에 대한 대비책
};
};
fallback 옵션 (없는 경로로 요청시)
- false: 404 Not Found로 반환
- blocking: 즉시 생성 (Like SSR)
- true: 즉시 생성 + 페이지만 미리 반환 (먼저 레이아웃만 반환 후 데이터를 반환 함)
- 사전 렌더링
- 결과: 3개의 페이지 렌더링
- book/1.html
- book/2.html
- book/3.html
- 결과: 3개의 페이지 렌더링
ISR 증분 정적 재생성 (Incremental Static Regeneration)
단순히 SSG 방식으로 생성된 정적 페이지를 일정 시간을 주기로 다시 생성하는 기술
- SSG의 특성으로 한번 생성된 데이터는 새롭게 업데이트가 되더라도 해당 페이지가 업데이트 되지는 않음
- SSG와 SSR의 장점을 결합한 방식임
- 단 설정된 유통기한의 시간이 종료되기 전까지는 기존 내용이 보여지고, 해당 유통기한 시간이 지나면 새롭게 업데이트 됨.
export const getStaticProps = async () => {
const [allBooks, recoBooks] = await Promise.all([fetchBooks(), fetchRandomBooks()]);
return { props: { allBooks, recoBooks }, revalidate: 3 };
};
-
revalidate 3초 간격으로 재 생성
-
시간 기반의 ISR을 적용하기 어려운 페이지도 존재함
-
시간과 관계없이 사용자의 행동에 따라 데이터가 업데이트 되는 페이지
- 게시글 수정: 페이지 데이터를 업데이트 해야 함
- 게시글 삭제: 페이지를 제거해야 함
- 요청을 받을 때마다 페이지를 다시 생성하는 (On-Demand ISR)
- 수정/삭제시 revalidate 요청
SEO 설정
import Head from "next/head";
import Head from "next/document" // 이 모듈은 _document.tsx 에서 사용됨
const Home = () => {
return (
<>
<Head>
<title>한입북스</title>
<meta property="og:image" content="/thumbnail.png" />
<meta property="og:title" content="한입북스" />
<meta property="og:description" content="한입 북스에 등록된 도서들을 만나보세요." />
</Head>
</>
);
}
- og의 경우 opengraph의 약자
- og:image: 카카오톡, 페이스북 등 각종 SNS 에서 미리 보여질 섬네일 이미지
- og:title: 카카오톡, 페이스북 등 각종 SNS 에서 미리 보여질 페이지 제목
- og:description: 카카오톡, 페이스북 등 각종 SNS 에서 미리 보여질 페이지 설명
배포
- 배포방법은 vercel 로 배포하는 방법이 있음
# sudo npm i -g vercel (windows의 경우 powersheel, cmd를 관리자 권한)
# vercel login
# vercel --prod (바로 production 모드로 컴파일됨)
Page Router 단점
- 페이지별 레이아웃 설정이 번거롭다.
- Page.getLayout 설정의 문제
- 데이터 페칭이 페이지 컴포넌트에 집중된다.
- getServerSideProps, ... 등 페이지 컴포넌트에 항상 Props로 전달해야하는 문제
- 불 필요한 컴포넌트들도 JS Bundle에 포함된다.
- FCP 시점 이후 하이드레이션을 위해 JS Bundle로 전달하는 과정에 불필요한 컴포넌트도 전달하는 문제
- 상호작용이 필요없는 컴포넌트 조차도 하이드레이션이 이루어지는 문제