정적 생성에 따른 동적 라우트
/posts에 들어가있는 md파일로 /posts/<마크다운 파일명>
의 경로를 가지는 페이지를 동적으로 생성하려고 한다
이 프로젝트의 경우에는 /posts에 ssg-ssr.md
와 pre-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-ssr 나 http://localhost:3000/posts/pre-rendering 페이지가 정상적으로 출력된다
마크다운 구현
메타 데이터를 가져와서 보여주는 것은 성공했으니 마크다운을 가져와서 보여주자
마크다운을 html 문자열로 변환하는데 라이브러리의 도움이 필요하다
remark
와 remark-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].js
는 posts/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 |