[React] NextJs로 앱 만들기(2)

노유성·2023년 5월 27일
0
post-thumbnail

⭐getStaticProps를 이용한 포스트 리스트 나열

🪐getStaticProps

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이 넘겨주는 데이터를 컴포넌트에서 받아와야 한다.

🪐Home component

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를 갖고있는 지를 정의해놓는 부분이다. 헷갈릴 수도 있으니 주의깊게 보자.

🪐UI 작성하기

<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를 만들어 반환한다.

🪐Home.module.css

.listItem {
  margin: 0 0 1.25rem;
}

.lightText {
  color: #666;
}

⭐포스트 자세히 보기 페이지로 이동

🪐파일 기반 네비게이션 기능

React는 route을 위해서 react-router라는 라이브러리를 사용하지만 NextJS에서는 페이지 개념을 기반으로 구축된 파일 시스템 기반 라우터가 있다.

파일이 페이지 디렉토리에 추가되면 자동으로 경로로 사용할 수 있다.

기존에 react에서는 위와 같이 경로와 컴포넌트를 매핑해서 페이지를 보여주었다고 하면

Link 컴포넌트를 이용해서 특정 디렉토리에 있는 컴포넌트를 특정 경로로 바로 가져올 수 있다. 위 예시는

index.tsx에서 작성된 코드이며 같은 디렉토리에 존재하는 posts 폴더 하위에 존재하는 [id].tsx 파일의 컴포넌트를 호출하는 것이다.

🪐포스트 데이터 가져오기

☄️Post 데이터, 경로 목록 가져오기

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 페이지를 보여준다.

☄️전달받은 id로 포스트 데이터 가져오기

🌈 remark library

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로 전달된다.

🪐UI, [id].tsx

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

dangerouslySetInnerHTML은 React에서 사용되는 속성(property)으로, HTML 콘텐츠를 React 컴포넌트에 안전하게 삽입하는 데 사용됩니다. 이름에 "dangerously"가 포함된 이유는 이 속성을 사용할 때 주의가 필요하며, 안전하지 않은 사용으로 인해 보안 문제가 발생할 수 있다는 경고를 의미합니다.

<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />

이 부분은 우리가 matter로 parsing하고 remark라이브러리로 마크다운으로 작성된 post를 html도 바꾼 postData.contentHTML을 삽입하는 코드이다.

{postData.contentHTML}로 데이터를 삽입하게 되면은 태그가 파싱되지 않고 그대로 문자열로 출력되기 때문에 위와 같이 작성해야 한다.

여기까지 진행하면 자세히보기 페이지가 완성된다.

profile
풀스택개발자가되고싶습니다:)

0개의 댓글