본문으로 바로가기

Next.js로 블로그 만들기 - 4

category Next.js 2021. 3. 30. 17:04

정적 생성에 따른 동적 라우트

/posts에 들어가있는 md파일로 /posts/<마크다운 파일명>의 경로를 가지는 페이지를 동적으로 생성하려고 한다

이 프로젝트의 경우에는 /posts에 ssg-ssr.mdpre-rendering.md를 가지고 있으니 각각 /posts/ssg-ssr/posts/pre-rendering 의 경로를 갖게된다

동적 url를 위한 getStaticPaths 구현

먼저 동적 url를 가질 페이지를 만들어야한다

pages/posts에 [id].js라는 파일을 만들자 []로 만들어진 페이지들은 동적으로 생성될 페이지를 의미한다

다음은 lib/posts.js에 다음과 같은 함수를 추가해주자

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

    return fileNames.map(fileName => {
        return {
            params: {
                id: fileName.replace(/\.md$/, '')
            }
        }
    })
}

이 함수는 getStaticPaths에 필요한 배열을 리턴하는데 /posts에 있는 파일들의 이름을 배열에 담아 다음과 같은 형태로 리턴한다

[
  {
    params: {
      id: 'ssg-ssr'
    }
  },
  {
    params: {
      id: 'pre-rendering'
    }
  }
]

이때 배열에 담기는 객체의 형태가 중요한데 params라는 키를 반드시 포함해야하며

우리가 위에서 만들어준 동적페이지인 [id].js의 id값으로 쓰일 id값이 params에 존재해야한다

그리고 pages/posts/[id].js에 이 함수를 불러들이자

import React from 'react';
import Layout from "../../components/Layout";
import {getAllPostIds} from "../../lib/posts";

const Post = () => {
    return (
        <Layout>

        </Layout>
    );
};

export default Post;


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

여기서 리턴되는 paths값이 [id]를 위해 필요한 값이며 이제 동적 url를 위한 준비는 끝났지만

내부에 렌더링하기 위한 데이터를 불러오는 부분이 없다

정적 생성을 위한 getStaticProps 구현

lib/posts.js에 데이터를 가져오기 위한 함수를 하나 더 생성해야한다

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

    const matterResult = matter(fileContents)

    return {
        id,
        ...matterResult.data
    }
}

id값을 받아서 포스트에 들어갈 데이터를 가져오는 함수이다

이제 pages/posts/[id].js에서 getStaticProps를 내보내자

const Post = ({postData}) => {
    return (
        <Layout>
            {postData.title}
            <br />
            {postData.id}
            <br />
            {postData.date}
        </Layout>
    );
};
...
export async function getStaticProps({params}){
    const postData = getPostData(params.id);
    return {
        props : {
            postData
        }
    };
}

이제 url의 id값을 getPostData에 전달하고 해당하는 postData를 불러와서 props로 전달받게 된다

이제 http://localhost:3000/posts/ssg-ssrhttp://localhost:3000/posts/pre-rendering 페이지가 정상적으로 출력된다

마크다운 구현

메타 데이터를 가져와서 보여주는 것은 성공했으니 마크다운을 가져와서 보여주자

마크다운을 html 문자열로 변환하는데 라이브러리의 도움이 필요하다

remarkremark-html을 설치하자

그리고 lib/posts.js의 메타데이터만 받아오던 getPostData를 마크다운 또한 받아올 수 있도록 수정하자

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
  }
}

함수가 비동기로 바뀌었으니 [id].js에서 getStaticProps함수에 async, 받아오는 부분에 await를 추가하고

받아온 데이터를 출력할 수 있게 수정한다

const Post = ({postData}) => {
    return (
            ...
            {postData.date}
            <br/>
            <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
        </Layout>
    );
};

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

제목, 날짜, CSS 추가하기

이제 거의 틀은 완성되었으니 다듬을 차례이다

title

생성된 html의 메타데이터의 title을 수정하고 싶으면 next의 Head컴포넌트를 사용하면된다

    ...
    return (
        <Layout>
            <Head>
                <title>{postData.title}</title>
            </Head>
            {postData.title}
    ...

date

모든 페이지에는 생성한 날짜가 들어가있다 좀 더 멋있는 날짜 형식을 위해 date-fns라이브러리를 설치하자

날짜를 보여주는 것은 따로 데이터를 가지고 있지 않고 받은 데이터를 보여주기만하는 UI이고, post가 아닌 다른 페이지에서도 쓸 가능성이 있으므로

이후 재사용을 위해서 components/Date.js라는 컴포넌트를 생성해서 사용하자

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>
}

CSS

import utilStyles from '../../styles/utils.module.css'
...
            </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 페이지 수정하기

각각의 포스트 페이지들은 꾸밈을 마쳤지만 인덱스 페이지에서 포스트로 접근하는 링크는 아직 존재하지 않아서 링크를 직접 타이핑해야한다

동적 링크를 만드는 방법은 다음과 같다

<Link href="/posts/[id]" as={/posts/${id}}>

실제 주소를 가지고 있는 as와 href를 같이 사용해 주어야한다

<Link href={/posts/${id}}>를 사용해도 구동에 문제는 없던데 어째서 위의 경우처럼 했는지는 의문...

어떻게 검색해야할지도 모르겠다

그래서 index.js 를 다음과 같이 수정해주면된다

export default function Home({allPostsData}) {
  return (
      <Layout home>
        <Head>
          <title>{siteTitle}</title>
        </Head>
        <section className={utilStyles.headingMd}>
          <p>Hello</p>
          <p>
            (This is a sample website - you’ll be building a site like this on{' '}
            <a href="https://nextjs.org/learn">our Next.js tutorial</a>.)
          </p>
        </section>
          <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
              <h2 className={utilStyles.headingLg}>Blog</h2>
              <ul className={utilStyles.list}>
                  {allPostsData.map(({id, date, title})=>{
                      return (
                          <li className={utilStyles.listItem} key={id}>
                              <Link href="/posts/[id]" as={`/posts/${id}`}>
                                  <a>{title}</a>
                              </Link>
                              <br />
                              <small className={utilStyles.lightText}>
                                  <Date dateString={date} />
                              </small>
                          </li>
                      )
                  })}
              </ul>
          </section>
      </Layout>
  )
}

동적 라우트

getStaticPaths

getStaticPaths 또한 getStaticProps처럼 외부 데이터, API, 데이터베이스에서 가져올 수 있다

fallback

getStaticPaths함수에서 객체를 리턴할 때 경로값을 위한 데이터(paths)만 전달한 것이 아니라 fallback또한 같이 전달해주었는데

이 fallback이 false이면 paths로 전달받은 외의 경로는 모두 404에러가 발생한다

ture로 설정되어있다면 빌드시 생성되지 않은 페이지에 접근할 때 404에러가 아닌 fallback페이지를 전달한다

포괄 라우트

동적 라우트는 대괄호안에 ...를 추가하는 것 만으로 모든 경로를 확장할 수 있다

예를 들어 pages/posts/[...id].jsposts/a, posts/a/b, posts/a/b/c 모두와 매치된다

이렇게 라우트를 확장했을 때는 getStaticPaths에서 리턴할 때 params.id값을 배열로 주어야한다

return [
  {
    params: {
      id: ['a', 'b', 'c']
    }
  }
  //...
]

'Next.js' 카테고리의 다른 글

Next.js로 블로그 만들기 - 5  (0) 2021.04.06
Next.js로 블로그 만들기 - 3  (0) 2021.03.28
Next.js로 블로그 만들기 - 2  (0) 2021.03.27
Next.js로 블로그 만들기 - 1  (0) 2021.03.25
next.js 란?  (0) 2021.03.21