export const getStaticProps: GetStaticProps = async () => {
const allPostData = getSortedPostData();
return {
props: {
allPostData
}
}
}
index.tsx 파일에 getStaticProps 함수를 정의한다. 함수명을 적을 때에는 함수 이름에 토씨 하나도 틀려서는 안된다. 그 다음 type annotation을 통해서 함수 type을 지정해주고 getStaticProps는 async 함수로 정의해주어야 한다. 또한 데이터를 가져오고자 하는 컴포넌트의 파일 안에 getStaticProps 함수를 정의해주어야 한다.
그 다음 lib/posts에 정의해두었던 getSortedPostData()를 이용해 데이터를 가져온다. 그 후에 받은 데이터를 props에 넣어서 반환한다. 그 다음 getStaticProps이 넘겨주는 데이터를 컴포넌트에서 받아와야 한다.
const Home = ({ allPostData }: {
allPostData: {
date: string
title: string
id: string
}[]
}) => {
return (
...
위와 같이 props로 넘겨준 데이터에서 allPostData를 distructuring 해서 가져온다. 또 가져온 데이터에 대해서 type annotation을 해주어야 하기에 type annotation도 해준다. distructuring을 할 때 { } 안에 넣어서 가져오기 때문에 마찬가지로 type도 { }안에서 annotatino 해준다.
allPostData: {
date: string
title: string
id: string
}[]
이 부분에서 []는 가져오는 데이터 타입이 배열임을 의미하고 { }는 각 배열의 요소가 object이며 어떤 property를 갖고있는 지를 정의해놓는 부분이다. 헷갈릴 수도 있으니 주의깊게 보자.
<ul className={homeStyles.list}>
{allPostData.map(({ id, date, title }) => (
<li className={homeStyles.listItem} key={id}>
<a>{title}</a>
<br/>
<small className={homeStyles.lightText}>
{date}
</small>
</li>
))}
</ul>
allPostData는 배열임으로 map 메소드로 순회할 수 있다. 넘어오는 요소들은 객체이므로 해당 객체의 property를 distructuring을 통해서 가져온 후에 li를 만들어 반환한다.
.listItem {
margin: 0 0 1.25rem;
}
.lightText {
color: #666;
}
React는 route을 위해서 react-router라는 라이브러리를 사용하지만 NextJS에서는 페이지 개념을 기반으로 구축된 파일 시스템 기반 라우터가 있다.
파일이 페이지 디렉토리에 추가되면 자동으로 경로로 사용할 수 있다.
기존에 react에서는 위와 같이 경로와 컴포넌트를 매핑해서 페이지를 보여주었다고 하면
Link 컴포넌트를 이용해서 특정 디렉토리에 있는 컴포넌트를 특정 경로로 바로 가져올 수 있다. 위 예시는
index.tsx에서 작성된 코드이며 같은 디렉토리에 존재하는 posts 폴더 하위에 존재하는 [id].tsx 파일의 컴포넌트를 호출하는 것이다.
lib/posts.tsx
const postsDirectory = path.join(process.cwd(), 'posts');
export function getAllPostIds() {
const fileNames = fs.readdirSync(postsDirectory);
return fileNames.map(fileName => {
return {
params: {
id:fileName.replace(/\.md$/, '')
}
}
})
}
fs 모듈을 이용해서 posts 디렉토리에 존재하는 모든 파일의 이름을 params에 담아서 배열로 반환한다.
위와 같이 데이터는 반환된다. params를 property로 가지고, params의 value도 id 를 property로 가진 object들을 배열로 반환한다.
posts/[id].tsx
export const getStaticPaths: GetStaticPaths = async() => {
const paths = getAllPostIds();
console.log('paths', paths)
return {
paths,
fallback: false
}
}
GetStaticPaths를 type annotatino으로 지정한 async 함수이다. 사전에 정의한 getAllPostIds() 함수를 이용해서 전체 포스트의 id값들을 가져온다. 그 다음 fallback을 false로 하여 값을 전달한다.
여기서 fallback이 false이면 올바르지 않은 경로를 사용자가 요청함녀 404페이지를 보여주고 true이면 사전에 만들어둔 fallback 페이지를 보여준다.
remark는 JavaScript에서 사용되는 마크다운(Markdown) 처리 도구로, 텍스트 기반 문서를 구문 분석하고 변환하는 기능을 제공하는 라이브러리입니다. 주로 문서 처리, 정적 사이트 생성, 블로그 엔진, 문서 변환 도구 등 다양한 목적으로 활용됩니다.
-chatGPT
npm install remark remark-html --save
lib/posts.tsx
const postsDirectory = path.join(process.cwd(), 'posts');
export async function getPostData(id: string) {
const fullPath = path.join(postsDirectory, `${id}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf-8');
const matterResult = matter(fileContents);
const processedContent = await remark().use(remarkHtml).process(matterResult.content);
const contentHtml = processedContent.toString();
return {
id,
contentHtml,
...(matterResult.data as {date: string; title: string;})
}
}
먼저 getPostData() 함수는 인자로 id값을 받는다. 그 다음 fullPath를 만들어준다. fullPath는 기존에 posts 폴더의 경로에 인자로 전달받은 id값과 확장자를 더해 post의 경로를 명시한 변수이다. 그 다음 fs모듈을 이용해 fileContents에 id에 매치되는 포스트의 내용을 가져온다.
그 다음 matter 모듈들 이용해서 parsing한 값을 넣어준다. 마지막으로 remark 모듈을 이용해서 html파일로 변환한다. 마지막으로 변환받은 값을 string으로 바꿔준다.
여기까지 수행하면은 contentHtml에는 id값에 매치되는 포스트의 내용이 html로 변환된 값이 들어가있을 것이다. 이 함수의 목적이 post의 내용을 반환하는 것이므로 id값과 contentHtml 그 다음 matter()이 파싱한 값중에 날짜와 타이틀을 property로 하는 object를 반환한다.
posts/[id].tsx
export const getStaticProps: GetStaticProps = async({params}) => {
const postData = await getPostData(params.id as string);
return {
props: {
postData
}
}
}
그 다음 빌드 전에 데이터를 가져오는 부분이다. 당연하게도 posts/id 페이지에서 필요한 내용이기에 [id].tsx에 정의해놓았다. 먼저 params를 인자로 받는다. getStaticProps()는 getStaticPaths()가 만든 배열 중에서 동적 경로에서 parameter값을 포함한 object를 인자로 받는다.
이 경로로 요청을 보낸다면 getStaticProps()에서 인자로 받는 값은
이다. 즉, 저 객체는 params의 값이다.
그 다음 getPostsData()를 이용해서 인자에 params의 id를 전달해주면 postData에는 id, contentHtml, date, title을 property로 하는 object가 할당된다. 해당 값을 props에 넣어서 리턴하면 컴포넌트에게 props로 전달된다.
export default function post({postData}: {
postData :{
id: string;
title: string;
date: string;
contentHtml: string;
}
}) {
return (
<div>
<Head>
<title>
{postData.title}
</title>
</Head>
<article>
<h1 className={homeStyles.headingXL}>
{postData.title}
</h1>
<div>
{postData.date}
</div>
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
</article>
</div>
)
}
먼저 인자로 postData를 구조분해할당으로 받고 인자로 받은 값에 type annotation을 해준다.
dangerouslySetInnerHTML은 React에서 사용되는 속성(property)으로, HTML 콘텐츠를 React 컴포넌트에 안전하게 삽입하는 데 사용됩니다. 이름에 "dangerously"가 포함된 이유는 이 속성을 사용할 때 주의가 필요하며, 안전하지 않은 사용으로 인해 보안 문제가 발생할 수 있다는 경고를 의미합니다.
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
이 부분은 우리가 matter로 parsing하고 remark라이브러리로 마크다운으로 작성된 post를 html도 바꾼 postData.contentHTML을 삽입하는 코드이다.
{postData.contentHTML}로 데이터를 삽입하게 되면은 태그가 파싱되지 않고 그대로 문자열로 출력되기 때문에 위와 같이 작성해야 한다.
여기까지 진행하면 자세히보기 페이지가 완성된다.