메인 콘텐츠로 건너뛰기

Blog

로그인
Next.js(v14)

Next.js 14 (Page Router 방식)

쿼리스트링 사용방법

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컴포넌트 보다 먼저 실행되어서 해당 컴포넌트에 필요한 데이터를 다른 백엔드 서버에서 불러오는 함수
    • 절차
      1. localhost:3000/index 접속
      2. ➡️ index.tsx의 getServerSideProps 실행
      3. ➡️ 최종 페이지 컴포넌트 실행
  • 서버컴포넌트 (페이지 컴포넌트) 인경우에는 클라이언트 객체 사용안됨.

  • 단, 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 사전렌더링

  1. 경로 설정이 필요
    1. id: 1
    2. id: 2
    3. 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: 즉시 생성 + 페이지만 미리 반환 (먼저 레이아웃만 반환 후 데이터를 반환 함)
  1. 사전 렌더링
    1. 결과: 3개의 페이지 렌더링
      1. book/1.html
      2. book/2.html
      3. book/3.html

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 단점

  1. 페이지별 레이아웃 설정이 번거롭다.
    1. Page.getLayout 설정의 문제
  2. 데이터 페칭이 페이지 컴포넌트에 집중된다.
    1. getServerSideProps, ... 등 페이지 컴포넌트에 항상 Props로 전달해야하는 문제
  3. 불 필요한 컴포넌트들도 JS Bundle에 포함된다.
    1. FCP 시점 이후 하이드레이션을 위해 JS Bundle로 전달하는 과정에 불필요한 컴포넌트도 전달하는 문제
    2. 상호작용이 필요없는 컴포넌트 조차도 하이드레이션이 이루어지는 문제

댓글 0