본문으로 바로가기
← 목록으로
frontendjavascriptfrontendnext.js

Next.js 14 (Page Router 방식)

 · 읽는 시간 약 16

Next.js의 Page Router 기능을 설명한 블로그 글입니다. 쿼리스트링 처리, 네비게이션, 스타일링, 레이아웃 설정, 페이지 이동, 프리페칭, API 라우트, 데이터 페칭(SSR, SSG, ISR), SEO 설정, 배포 방법 등을 다룹니다. 마지막으로 Page Router의 단점으로 페이지별 레이아웃 설정의 번거로움, 페이지 컴포넌트에 집중된 데이터 페칭, 불필요한 컴포넌트까지 JS Bundle에 포함되는 문제를 언급합니다.

쿼리스트링 사용방법

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

© 2026 Kim Zero. All rights reserved.