Next.js 공식 홈페이지에서 제공하는 튜토리얼 step 6

이승재·2022년 1월 3일
0

Next.js

목록 보기
6/9

step 5 와 이어집니다.

Dynamic Routes

Next.js 에서 동적 라우팅은 어떤식으로 하는지 알아봅시다.

외부 데이터에 의존하는 page path

우리는 이전시간에 외부 데이터에 따라 페이지 내용이 바뀌는 것을 구현해 보았습니다.

이번 step 에서는 각 페이지 경로가 외부 데이터에 의존하는 경우에 대해 알아 볼 것입니다. Next.js를 사용하면 외부 데이터에 의존하여 동적 라우팅을 할 수 있습니다.

How to Statically Generate Pages with Dynamic Routes

우리의 예제 코드에서는 블로그 게시물에 대한 동적 경로를 만들고자 합니다.

  • 우리는 각 게시물이 /posts/<id> 경로를 갖기를 원하며, <id> 우리가 작성했었던 마크다운 파일의 이름이다.
  • ssg-ssr.mdpre-rendering.md이 있으므로 경로는 /filename/ssg-filename/filename/pre-fre-filename겠죠.

next.js 의 pages 내부에 [id].js 와 같이 대괄호로 감싸져 있는 파일은 동적으로 라우팅 된 파일입니다.

pages/posts/[id].js는 이런식으로 작성 할 수있을 것 입니다.

  • pages/posts/[id].js
import Layout from '../../components/layout'

export default function Post() {
  return <Layout>...</Layout>
}

export async function getStaticPaths() {
  // Return a list of possible value for id
}

export async function getStaticProps({ params }) {
  // Fetch necessary data for the blog post using params.id
}

이 페이지에서 getStaticPaths라는 비동기 함수를 내보냅니다.
이 함수에서 우리는 동적으로 라우팅 된 id 을 지정해줍니다.
그리고 getStaticProps를 이용해 id 값으로 post 내용을 가져와 pre-rendering 을 위한 정적생성 과정을 진행 할 수있습니다.

어떻게 작성할 지 감이 오시나요?

한번 같이 작성 해봅시다.

일단 pages/posts/[id].js 을 다음과 같이 작성해 보세요

import Layout from '../../components/layout'

export default function Post() {
  return <Layout>...</Layout>
}

...은 나중에 채워 나가 봅시다.

그다음 lib/posts.js 파일에서 파일을 id(파일 명)를 가져올 수 있는 함수를 하나 만들어 줍니다

export function getAllPostIds() {
  const fileNames = fs.readdirSync(postsDirectory)

  // 이런식으로 반환 할 겁니다.
  // [
  //   {
  //     params: {
  //       id: 'ssg-ssr'
  //     }
  //   },
  //   {
  //     params: {
  //       id: 'pre-rendering'
  //     }
  //   }
  // ]
  return fileNames.map(fileName => {
    return {
      params: {
        id: fileName.replace(/\.md$/, '')
      }
    }
  })
}

중요!! 반환된 목록은 단순한 문자열의 배열이 아니라 위의 주석처럼 보이는 객체의 배열이어야 합니다.
각 개체에는 params 키가 있어야하며 id 키가있는 개체가 포함되어 있어야합니다 (파일 이름에 [id]를 사용하기 때문에). 그렇지 않으면 getStaticPaths가 동작 하지 않습니다.

우리가 만든 getAllPostIds 함수를 가져와 [id].js의 getStaticPaths 내에서 사용합니다.
다음과 같이 [id].js 에 코드를 추가해 보세요

import { getAllPostIds } from '../../lib/posts'

export async function getStaticPaths() {
  const paths = getAllPostIds()
  return {
    paths,
    fallback: false
  }
}

getStaticProps 부분을 마저 작성해 봅시다.

우리는 주어진 아이디로 게시물을 렌더링하기 위해 필요한 데이터를 가져와야 합니다. 블로그 포스트 내용이 되겠죠.

/lib/posts.js에 아이디를 통해 포스트 내용을 가져오는 함수를 추가해 봅시다

export function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`)
  const fileContents = fs.readFileSync(fullPath, 'utf8')

  // Use gray-matter to parse the post metadata section
  const matterResult = matter(fileContents)

  // Combine the data with the id
  return {
    id,
    ...matterResult.data
  }
}

그리고 이 함수를 사용할 [id].js 로 import 해옵시다. 그리고 이전에 배운것 처럼 getStaticProps도 구현해 줍니다.

import { getAllPostIds, getPostData } from '../../lib/posts'

export async function getStaticProps({ params }) {
  const postData = getPostData(params.id)
  return {
    props: {
      postData
    }
  }
}

그리고 가져온 데이터를 통해서 [id].js에 있는 Post() 컴포넌트를 작성해 봅시다.

export default function Post({ postData }) {
    return (
        <Layout>
            {postData.title}
            <br />
            {postData.id}
            <br />
            {postData.date}
        </Layout>
    )
}

그리고 잘 동적 라우팅이 되었는지 다음 주소에 가서 확인해 보세요.

잘 된다면, 이제 md로 작성된 안에 포스트 내용들을 채워 봅시다.

이 내용을 웹에 뿌리기 위해선 remark 라는 패키지를 받아줍니다.

npm install remark remark-html

그리고 lib/posts.js 파일에서 remark 를 import 하고 id를 통해 정보들을 가져오는 함수 getPostData() 를 다음과 같이 수정해 보세요.

import { remark } from 'remark'
import html from 'remark-html'
export async function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`)
  const fileContents = fs.readFileSync(fullPath, 'utf8')

  // Use gray-matter to parse the post metadata section
  const matterResult = matter(fileContents)

  // Use remark to convert markdown into HTML string
  const processedContent = await remark()
    .use(html)
    .process(matterResult.content)
  const contentHtml = processedContent.toString()

  // Combine the data with the id and contentHtml
  return {
    id,
    contentHtml,
    ...matterResult.data
  }
}

remake를 하는 시간을 기다려야 하므로 비동기 처리를 위해 async / await 를 사용했습니다.

이제 받아온 content를 뿌려볼 시간입니다. [id].js 를 다음과 같이 작성해 보세요

import Layout from '../../components/layout'
import { getAllPostIds, getPostData } from '../../lib/posts'

export default function Post({ postData }) {
    return (
        <Layout>
            {postData.title}
            <br />
            {postData.id}
            <br />
            {postData.date}
            <br />
            <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
        </Layout>
    )
}

export async function getStaticPaths() {
    const paths = getAllPostIds()
    return {
        paths,
        fallback: false
    }
}

export async function getStaticProps({ params }) {
    const postData = await getPostData(params.id)
    return {
        props: {
            postData
        }
    }
}

가져온 md text를 <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
를 통해 표출하고 getStaticPropsgetPostData 의 비동기 처리를 위해 await 키워드를 붙여 작성하였습니다.

다시 한번 접속해 보면 작성된 콘텐츠가 표시 되어 있는 것을 확인 할 수 있습니다.

여기까지 동적 라우팅을 하는 방법을 알아 보았으니 우리가 짠 코드를 좀더 이쁘게 보여줄 수 있도록 정리를 해봅시다.

일단 posts의 title을 전에 배웠던 Head 태그를 사용하여 title을 변경시켜 봅시다.

[id].js에 다음 코드를 추가해 보세요

// Add this import
import Head from 'next/head'

export default function Post({ postData }) {
  return (
    <Layout>
      {/* Add this <Head> tag */}
      <Head>
        <title>{postData.title}</title>
      </Head>

      {/* Keep the existing code here */}
    </Layout>
  )
}

이제 다음과 같이 날짜 포맷을 좀더 이쁘게 변경해 봅시다.

날짜 포멧팅을 해주는 패키지를 다음 명령어를 통해 설치해 줍니다.

npm install date-fns

그다음 components/date.js 를 생성하고 Date 컴포넌트를 다음과 같이 작성해 보세요.

import { parseISO, format } from 'date-fns'

export default function Date({ dateString }) {
  const date = parseISO(dateString)
  return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
}

그리고 이 컴포넌트를 사용하여 [id].js 에서 날짜 표시하는 곳을 다음과 같이 변경해 봅시다.

// Add this import
import Date from '../../components/date'

export default function Post({ postData }) {
  return (
    <Layout>
      {/* Keep the existing code here */}

      {/* Replace {postData.date} with this */}
      <Date dateString={postData.date} />

      {/* Keep the existing code here */}
    </Layout>
  )
}

좀더 아름답게 보이기 위해서 우리가 작성한 utill.module.css 로 스타일을 다음과 같이 입혀 봅시다.

import utilStyles from '../../styles/utils.module.css'

export default function Post({ postData }) {
  return (
    <Layout>
      <Head>
        <title>{postData.title}</title>
      </Head>
      <article>
        <h1 className={utilStyles.headingXl}>{postData.title}</h1>
        <div className={utilStyles.lightText}>
          <Date dateString={postData.date} />
        </div>
        <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
      </article>
    </Layout>
  )
}

잘 동작하는 것을 확인 하실 수 있을 겁니다.

마저 index.js 파일도 수정해 볼까요.
우리는 Link 컴포넌트를 통해 path 를 이동시킬 수 있다는 것을 배웠었습니다. 이것을 통해 index.js 에서 동적 라우팅된 페이지로 가는 부분을 작성해 봅시다.

일단 다음 컴포넌트들을 import 해옵니다.

import Link from 'next/link'
import Date from '../components/date'

그리고 이전에 작성해둔 포스트 리스트를 작성하는 부분을 다음과 같이 작성해 봅시다.

<li className={utilStyles.listItem} key={id}>
  <Link href={`/posts/${id}`}>
    <a>{title}</a>
  </Link>
  <br />
  <small className={utilStyles.lightText}>
    <Date dateString={date} />
  </small>
</li>

잘 동작합니다. 여기까지 같이 오신분들 수고 많으셨습니다.

profile
웹개발이 하고싶어요

0개의 댓글