[Next.js] Page router에 대하여

Minhyuk Song·2024년 10월 24일
0

Next.js

목록 보기
2/2

_app.tsx, _document.tsx

_app.tsx에서는 모든 페이지 컴포넌트의 부모 컴포넌트이기에 헤더와 푸터와 같은 공통적인 요소를 넣을 수 있다. (간단히 말하면 루트 컴포넌트)

  • Component는 현재 페이지를 의미한다.
  • pageProps는 getInitialProps, getStaticProps, getServerSideProps 를 통해 가져온 초기 속성값을 의미한다. 위의 값들이 없다면 빈 객체를 반환합니다.
import '../styles/globals.css'
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

export default MyApp

_document.tsx는 모든 페이지에서 공통적으로 사용하는 html, head(meta) 혹은 body 태그 안에 속성을 추가할 때 사용한다.

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

페이지 라우팅

파일명 기반의 페이지 라우팅을 사용한다. 즉 파일을 만들어 라우팅을 만든다.
일반적인 경우가 아닌 경우를 대처하는 방법을 알아보자.

동적 경로

해당 경로에서 / 뒤에 있는 URL 파라미터들을 일일이 다 고려해서 파일을 만들 수 없기 때문에 동적 경로를 사용한다.

예시로 폴더/[id].tsx 라고 사용하여 동적 경로를 만들 수 있다.

쿼리스트링 혹은 URL 파라미터 가져오기

URL에 있는 쿼리스트링이나 URL 파라미터를 가져오기 위해서는 next/router에서 제공하는 useRouter를 사용한다.

페이지 라우터에서는 next/router, 앱 라우터에서는 next/navigation 에서 제공하는 useRouter를 구분해서 사용해야 한다.

서로 호환이 되지 않는다.

const router = useRouter();

const { id } = router.query;

Catch All Segment

1234/123/12/22 와 같이 URL 파라미터가 더 늘어날 경우까지 고려하기 위해서 Catch All Segment라는 문법을 사용하면 된다.

[...id].tsx

Optional Catch All Segment

Catch All Segment에서 잡지 못하는 경우가 URL 파라미터가 없고 index.tsx 가 없을 때 기본 경로로 접근할 때다.
이것까지 고려하기 위해 Optional Catch All Segment라는 문법을 사용한다.

[[...id]].tsx

네비게이팅

서버 사이드 렌더링을 사용하는 경우 일반적인 <a> 태그를 사용하면 클라이언트 측 라우팅이 적용되지 않아서 페이지 이동 시 서버에서 새로고침이 발생합니다. 이는 사용자 경험과 검색 엔진 최적화(SEO)에 부정적인 영향을 미칩니다.

반면 Link 컴포넌트를 사용하면 SPA의 경험을 제공하면서도 검색 엔진 최적화(SEO)에 유리한 이점도 가질 수 있습니다.

  • Link 컴포넌트는 페이지 이동을 위해 페이지를 새로고침하지 않음
  • 페이지 이동에 필요한 JavaScript 코드를 미리 로드하여, 검색 엔진 크롤러가 페이지의 콘텐츠를 미리 인지

router.push()

특정 버튼에 대한 동작이나 어떠한 이벤트로 인한 페이지 이동을 하기 위해서는 router 객체의 push()라는 메소드를 사용한다.

프리페칭

next.js에서는 자동 코드 분할을 해주기 때문에 JS 번들에서는 요청한 페이지에 대한 JS 내용이 담겨있다. 그래서 페이지를 이동할 때 다시 JS 번들을 불러오는 과정이 생긴다. 이를 막기 위해 프리페칭이라는 기능이 있다.

문제상황 이미지

현재 페이지에서 연결된 페이지 (즉 이동가능성이 있는 페이지)에 대한 JS 번들을 미리 로딩하는 걸 프로페칭이라고 한다.

router.prefetch()

Link 컴포넌트로 페이지 이동을 구현하면 이동할 페이지에 대한 프리페칭이 되지만, router.push()와 같은 프로그매틱한 방식은 프리페칭이 적용되지 않는다.
만약 router.push()로 이동할 페이지에 대한 프리페칭을 원한다면 router.prefetch()를 이용하면 된다.

const handleClick = () => {
 router.push('/이동할 페이지'); 
}

useEffect(()=> {
	router.prefetch('/이동할 페이지');
}, [])

prefetch={false}라는 속성을 추가하면 된다.

<Link href={"/이동할 페이지"} prefetch={false} />

레이아웃

page, layout, template 간단 비교

글로벌 레이아웃

_app.tsx에서 글로벌 레이아웃에 해당하는 내용을 작성을 하거나, 컴포넌트를 생성하면 된다. (GlobalLayout)

페이지별 레이아웃

특정 페이지에만 띄우길 원하는 레이아웃이 있다면 글로벌 레이아웃에서 쓰이는 방식과 같은 중첩 방식으로는 구현할 수 없다.

그래서 getLayout()를 사용하여야 한다.

// Page.tsx
import type { ReactElement } from 'react'
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
import type { NextPageWithLayout } from './_app'
 
const Page: NextPageWithLayout = () => {
  return <p>hello world</p>
}
 
Page.getLayout = (page: ReactElement) => (
  <Layout>
  	<NestedLayout>{page}</NestedLayout>
  </Layout>
)
 
export default Page

getLayout이 없을 경우도 고려하여 NextPageWithLayout, AppPropsWithLayout 타입을 만들어줍니다.

모든 페이지 컴포넌트에서 getLayout을 꺼내오고 적용하면 됩니다.

// _app.tsx
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
 
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode
}
 
type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}
 
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  const getLayout = Component.getLayout ?? ((page) => page)
 
  return getLayout(<Component {...pageProps} />)
}

다양한 사전 렌더링 방식

서버 사이드 렌더링 (SSR)


  • 요청이 들어올 때마다 사전 렌더링을 전제로 함
  • getServerSideProps() 사용

getServerSideProps()

getServerSideProps()는 컴포넌트보다 먼저 실행되어서, 컴포넌트에 필요한 데이터를 불러오는 함수입니다. 불러온 데이터는 props 객체에 담아서 전달해야 합니다.

import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getServerSideProps = (async () => {
  // Fetch data from external API
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo: Repo = await res.json()
  // Pass data to the page via props
  return { props: { repo } }
}) satisfies GetServerSideProps<{ repo: Repo }>
 
export default function Page({
  repo,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <main>
      <p>{repo.stargazers_count}</p>
    </main>
  )
}

불러온 데이터에 대한 타입은 InferGetServerSidePropsType<typeof getServerSideProps>으로 추론할 수 있습니다.

그리고 주의사항으로는 서버에서 렌더링이 되기 때문에 브라우저에서 제공되는 문법 (ex. window 객체)은 참조 에러가 뜨기 때문에 그 서버사이드렌더링을 하는 컴포넌트 내에서는 사용할 수 없습니다.

사용하기 위해서는 아래와 같은 방법을 사용하면 됩니다.

  • typeof으로 조건문
  • useEffect
  • dynamic

Next.js window 객체가 없다고 할 때

정적 사이트 생성 (SSG)


서버 사이드 렌더링에서 서버에서 매번 데이터를 요청을 하는데 데이터를 불러오는 속도가 느리다면 빌드 타임에 미리 데이터를 불러오는 SSG 방식이 도입됩니다.

  • 빌드 타임에 페이지를 미리 사전 렌더링을 함
  • 사전렌더링 과정에서 많은 시간이 걸리는 페이지더라도 사용자 요청에 매우 빠르게 응답
  • 매번 같은 내용의 페이지를 응답 (최신화가 안 될 수도 있음)

getStaticProps()

import type { InferGetStaticPropsType, GetStaticProps } from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getStaticProps = (async (context) => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo = await res.json()
  return { props: { repo } }
}) satisfies GetStaticProps<{
  repo: Repo
}>
 
export default function Page({
  repo,
}: InferGetStaticPropsType<typeof getStaticProps>) {
  return repo.stargazers_count
}

SSR 방식과 매우 유사하며 다른 점으로는 InferGetStaticPropsType<typeof getStaticProps>으로 prop에 담긴 데이터를 추론하면 됩니다.

동적 경로에 적용


동적 경로에서 어떠한 URL이 있을 수 있는지 빌드 타임에 알려주어야 합니다.
그러기 위해서 getStaticPaths()가 추가적으로 필요합니다.

  • paths 라는 배열을 가능한 페이지의 경우들을 반환
  • fallback 옵션 (false, true, 'blocking')
import type {
  InferGetStaticPropsType,
  GetStaticProps,
  GetStaticProps,
} from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getStaticPaths = (async () => {
  return {
    // paths라는 배열을 가능한 페이지 경우 (params)를 반환
    paths: [
      {
        params: {
          id: '1',
        },
      },
      {
        params: {
          id: '2',
        },
      },
      {
        params: {
          id: '3',
        },
      },
    ],
    fallback: true, // false or "blocking"
  }
}) satisfies GetStaticPaths
 
export const getStaticProps = (async (context) => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo = await res.json()
  return { props: { repo } }
}) satisfies GetStaticProps<{
  repo: Repo
}>
 
export default function Page({
  repo,
}: InferGetStaticPropsType<typeof getStaticProps>) {
  return repo.stargazers_count
}

getStaticPaths()의 fallback 옵션

  • false : 404 Not Found 반환
  • blocking : 즉시 생성 (마치 SSR 방식과 유사)
  • true : 즉시 생성 + 페이지만 미리 반환

false일 경우

blocking일 경우

true일 경우

정적 경로에 적용

일반적으로 getStaticProps를 이용하면 됩니다.

증분 정적 재생성 (ISR)

SEO 설정

profile
어제보다 더 나은 오늘을 만들 수 있게

0개의 댓글

관련 채용 정보