step 5 와 이어집니다.
Next.js 에서 동적 라우팅은 어떤식으로 하는지 알아봅시다.
우리는 이전시간에 외부 데이터에 따라 페이지 내용이 바뀌는 것을 구현해 보았습니다.
이번 step 에서는 각 페이지 경로가 외부 데이터에 의존하는 경우에 대해 알아 볼 것입니다. Next.js를 사용하면 외부 데이터에 의존하여 동적 라우팅을 할 수 있습니다.
우리의 예제 코드에서는 블로그 게시물에 대한 동적 경로를 만들고자 합니다.
<id>
경로를 갖기를 원하며, <id>
우리가 작성했었던 마크다운 파일의 이름이다.ssg-ssr.md
과 pre-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 }} />
를 통해 표출하고 getStaticProps
의 getPostData
의 비동기 처리를 위해 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>
잘 동작합니다. 여기까지 같이 오신분들 수고 많으셨습니다.