본문 바로가기

개발

Gatsby 블로그: 포스트에 카테고리 추가하기

gatsby-starter-blog

지난 포스트에서 설명한 gatsby-starter-blog를 기준으로 포스트에 카테고리를 추가하는 방법에 대해서 설명하려고 한다.

공식 문서에 Creating Tags Pages for Blog Posts라는 제목으로 비슷한 내용에 대한 설명이 있지만, 내 기준으로 처음 Gatsby를 접한 상태에서 보기에는 좀 어렵고 복잡했다. 그래서 카테고리 추가를 위한 최소한의 코드(?)를 소개하는 것을 목표로 포스트를 작성해보려고 한다.

gatsby-transformer-remark

gatsby-transformer-remark는 markdown 본문을 html코드로 변환하고, 해당 코드와 markdown에 포함된 여러 데이터를 우리가 사용하기 편리한 형태로 GraphQL에 추가해주는 플러그인이다. GraphiQL(개발 서버 실행시 사용 가능한 GraphQL 인터페이스)에서 보이는 allMarkdownRemark, markdownRemark가 바로 gatsby-transformer-remark 플러그인에 의해 추가된 것이다.

 

{
  allMarkdownRemark {
    nodes {
      id
      html
      frontmatter {
        title
        date
        description
      }
    }
  }
  markdownRemark(id: {eq: ""}) {
    id
  }
}

 

여기서 id는 각 markdownRemark 노드마다 추가된 값으로 개별 포스트의 id로 사용된다. html은 markdown에서 변환된 것으로, 실제 포스트 내용을 페이지에 삽입할 때 사용된다. 이외에도 다양한 데이터가 gatsby-transformer-remark 플러그인에 의해 자동으로 추가되는데, 그 중에서도 포스트에 카테고리를 추가하기 위해 살펴봐야 할 것은 frontmatter다.

 

frontmatter

개별 markdown 파일 content\blog\*\index.md 을 보면, 다음과 같은 코드가 상단에 공통적으로 포함되어 있는 것을 확인할 수 있다.

 

---
title:
date:
description:
---

 

여기에 작성된 값들이 frontmatter의 title, date, description 값이 되는 것이다. 즉 여기서 다음과 같이 category를 추가하면 GraphQL에서 category값을 사용할 수 있게 된다.

 

---
title:
date:
description:
category: "개츠비" // 추가
---

 

각 포스트 상단에 위와 같이 category를 추가했다면 다양한 방법으로 활용할 수 있다. 예를 들어, 특정 카테고리에 포함된 모든 포스트의 id를 불러오고 싶다면 다음과 같이 query를 작성하면 된다.

 

{
  allMarkdownRemark(filter: {frontmatter: {category: {eq: "개츠비"}}}) {
    nodes {
      id
    }
  }
}

 

모든 카테고리 값을 중복없이 나열하고 싶다면 다음과 같이 query를 작성할 수 있다.

 

{
  allMarkdownRemark {
    categoryList: distinct(field: frontmatter___category)
  }
}

 

활용 예시

목표

  • index 페이지: 모든 카테고리 페이지에 대한 링크 추가
  • 카테고리 페이지: 해당 카테고리에 포함된 포스트들만 화면에 표시

포스트에 category 추가

각 markdown 파일에 다음과 같이 category 값을 추가한다.

 

// hello-world/index.md
---
// ...
category: "Hello" 
---

// my-second-post/index.md
---
// ...
category: "Hello" 
---

// new-beginnings/index.md
---
// ...
category: "New" 
---

 

처음과 두 번째 포스트에는 "Hello"라는 카테고리를, 마지막 포스트에는 "New"라는 카테고리를 임의로 추가했다.

 

개별 카테고리 페이지 생성

gatsby-node.js 파일의 createPages 함수에 카테고리 목록을 불러오고, 각 카테고리마다 페이지를 생성하도록 하는 코드를 추가한다.

 

exports.createPages = async ({ graphql, actions, reporter }) => {
  // ...

  // 기존 쿼리 수정
  const result = await graphql(
    `
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: ASC }
          limit: 1000
        ) {
          // 추가
          categoryList: distinct(field: frontmatter___category)
          nodes {
            // ...
          }
        }
      }
    `
  )

  // ... 에러 처리, 포스트 페이지 생성 코드 생략

  // 카테고리 페이지에 사용될 템플릿 (컴포넌트)
  const categoryPosts = path.resolve(`./src/templates/category-posts.js`)

  // 카테고리 목록을 가져오고, 각 카테고리마다 페이지 생성
  const categories = result.data.allMarkdownRemark.categoryList
  categories.forEach(category => {
    createPage({
      path: `/${category}/`,
      component: categoryPosts,
      context: { category },
    })
  })
}

 

카테고리 페이지 템플릿 작성

src\pages\index.js 파일을 부분적으로 수정하여 특정 카테고리의 포스트만 화면에 표시하는 페이지 템플릿을 작성한다.

 

// src/templates/category-posts.js

const CategoryPost = ({ data, location, pageContext }) => {
  // ...

  // gatsby-node.js에서 context로 넘겨준 값
  const { category } = pageContext

  // ...

  return (
    <Layout location={location} title={siteTitle}>
      <Seo title={`Posts in ${category}`} /> {/* 페이지 title 수정 */}
      <Bio />
      <h3>{`Current: ${category}`}</h3> {/* 현재 카테고리 표시 */}
      <ol style={{ listStyle: `none` }}>
        {/* 생략 */}
      </ol>
    </Layout>
  )
}

export default CategoryPost

// 페이지 쿼리 수정
export const pageQuery = graphql`
  query($category: String!) {
    // ...
    allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC }
      // 추가된 필터
      filter: { frontmatter: { category: { eq: $category } } }
    ) {
      nodes {
        // ...
      }
    }
  }
`

 

gatsby-node.js 파일에서 context로 넘겨준 category 값이 component 내에서 그리고 page query에서 포스트를 필터링하기 위해서 활용되는 것을 확인할 수 있다.

 

홈에서 카테고리 목록 표시

다음으로 src\pages\index.js 파일을 수정하여 카테고리 목록과 해당 페이지로의 링크를 보여준다.

 

const BlogIndex = ({ data, location }) => {
  // ...

  // 카테고리 목록
  const categories = data.allMarkdownRemark.categoryList

  // ...

  return (
    <Layout location={location} title={siteTitle}>
      <Seo title="All posts" />
      <Bio />
      <nav> {/* 카테고리 Nav */}
        <ul>
          {categories.map(category => (
            <li key={category}>
              <Link to={`/${category}/`}>{category}</Link>
            </li>
          ))}
        </ul>
      </nav>
      <ol style={{ listStyle: `none` }}>
        {/* 생략 */}
      </ol>
    </Layout>
  )
}

export default BlogIndex

export const pageQuery = graphql`
  query {
    // ...
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
      // 추가
      categoryList: distinct(field: frontmatter___category)
      nodes {
        // ...
      }
    }
  }
`

 

완성

개발 서버를 실행시켜보면 홈 화면에는 카테고리 목록이 새롭게 추가되었고,

카테고리가 상단에 표시

 

특정 카테고리 링크를 클릭하면 markdown에 추가했던 category 값에 따라 그 카테고리에 해당되는 포스트만 화면에 나타나는 것을 확인할 수 있다.

해당 카테고리의 글만 노출

 

'개발' 카테고리의 다른 글

Gatsby 블로그: TOC(목차) 추가하기  (0) 2022.11.30
Gatsby 블로그: Pagination 추가하기  (0) 2022.11.30
Gatsby로 블로그 만들기  (0) 2022.11.30
HTTP 세션과 쿠키  (0) 2022.11.30
JavaScript 클로저  (0) 2022.11.30