Harry Park's Blog

Gatsby Blog의 Slug를 구성하기

과거에 작성한 HexoWordpress에 저장된 포스트를 가져오는 작업을 먼저 해보기로 결정했다. Gatsby 블로그의 레이아웃을 잡아가는 과정에서 몇 개 이상의 컨텐트가 필요하다고 생각했기 때문이다. 문서 자체는 markdown 파일이기에 복사 후 조금 수정해 주면 문제가 없다. 하지만 slug 구성과 폴더의 구조 결정이 우선적으로 이루어져야했다.

Slug가 무엇일까

이미 slug라는 단어에 이미 익숙한 사람들이 많을지 모르겠지만, 나에게는 새로운 용어 중 하나였다. wikipedia에서 문서를 찾았지만 역시 민달팽이에 대한 것이었고, url과 함께 검색한 결과로 Clean URL 문서를 찾을 수 있었다. 이 문서를 통해서 slug의 뜻과 유래를 알 수 있었다.

slug는 URL의 한 부분으로 사람이 읽기 쉬운 키워드로 페이지를 구분하게 해주는 키워드이다. 특정 리소스 혹은 파일의 이름, 페이지의 제목 등이 될 수 있다. 사람들은 이 slug를 통해서 해당 컨텐트의 내용을 제목을 짐작할 수 있다.

slug

slug은 자동 생성되거나 직접 입력되는데, 그 간결성을 유지하기 위해서 다음과 같은 몇가지 조건을 가진다. 너무 긴 제목은 가지지 않는다. 전체가 소문자인 영문자(Latin script)이다. 인코딩 되는 문자를 피하기 우위해서 (보기싫은 %20) 각 단어는 하이픈 - 혹은 언더스코어 -로 연결한다. 구두점을 사용하지 않으며 접속사 처럼 없어도 되는 단어는 생략한다.

Slug라는 단어의 유래

언제나 하는 고민이지만, 뜻을 알았으니 여기서 그만 멈춰야할지 잠시 고민 했다. 하지만 결국 단어의 유래도 알아보기로 한다. slug는 원래 뉴스 출판 업계에서 사용하고 있는 용어이다. 그에 대한 wikipedia 문서도 따로 존재했다. Slug_(publishing)

slug는 뉴스기사를 분류하는데 도움이되는 핵심 키워드이다. 예를 들면, 오바마 대통령에 대한 기사는 OBAMA라는 slug를 사용했다고 한다. slug의 또 다른 기능은 또 편집자에게 기사에 대한 특정 정보를 알려주기 위한 용도로도 사용 되었다. 예를 들면 AM은 아침 뉴스를 위한 기사, CX 이전 기사에 대한 수정(correction)을 나타낸다.

하지만 그 것의 어원은 따로 있었다. 과거 디지털 프린터의 시대가 오기 전에는 Hot metal typesetting 방식(한국어로는?)으로 조판을 하던 시절에 인쇄를 위한 판을 만들기 위해서 작업자가 일일이 금속으로 만들어진 활자를 배치해야 했는데, 그 금속 조각을 slug라고 한다.

slugs2 tmagArticle

Gatsby-starter-blog의 기본 Slug

gatsby-starter-blogcontent/blog 폴더를 기반으로 폴더의 계층 구조와 파일명을 통해서 slug를 생성한다. 그 예시는 다음과 같다.

폴더 구조Slug
content/blog/2020-11-25/hello-world.md/2020-11-25/hello-world
content/blog/hello-world.md/hello-world
content/blog/hello-world/index.md/hello-world

위에서 볼 수 있듯이 특정 디렉토리 안에서 hello-world/index.mdhello-world.md 는 동일한 slug를 생성한다. 하지만 이렇게 slug 이름이 겹칠 경우에는 hello-world.md가 우선순위를 갖도록 만들어져 있다. 그래서 hello-world/index.md는 페이지가 생성 되지 않으니 주의해야 한다.

어떻게 Slug를 구성할까?

전에 사용하던 방식도 있었고 여러 블로그를 둘러봤지만 가장 마음에 드는 형식은 만든 날짜를 path로써 포함 시키는 것이다. 예를 들면, 2020/11/25/hello-world 이렇게 구성하는 것이다. 물론 조금만 손 보면 2020-11-25-hello-world, 20201125/hello-world 등 그 어느 것도 가능하게 만들 수 있다는 점이 gatsby의 장점이다.

2020/11/25/hello-world의 slug 구성은 잘 디자인된 REST API 처럼 보인다. 단지 그렇게 보여서가 아니라, 2020 혹은 2020/11 과 같이 접근을 하면 2020년에 작성된 모든 포스트 혹은 2020년 11월에 작성된 모든 포스트를 그룹화해서 보여줄 수 있도록 확장하기 용이하기 때문이다. 나중에 알게 된 사실은 Wordpress에서 사용하던 방식이라는 것이다.

중첩된 폴더 이용할 때의 단점

이 부분에서 조금 고민이 있었는데, 폴더 구조 자체를 2020/11/25/hello-world/index.md와 같이 구성하면 현재 상태에서 아무것도 수정할 필요가 없다는 것이 장점이다. 하지만 직접 사용해본 경험에 의하면 폴더가 깊게 중첩될수록 관리가 귀찮아진다. index.md 파일 하나를 편집하기 위해서는 VS Code를 열고 7번의 클릭을 해야 한다.

하나의 폴더를 이용할 때의 단점

폴더를 아예 없애는 것 또한 단점이 존재한다. 이미지나 첨부파일들은 별도의 폴더로 나누어져야 관리가 편리하다. 그렇지 않으면 모든 파일들에 겹치지 않는 이름을 할당해야 하는데 가끔씩 그냥 temp.jpg 처럼 막 지어쓰고 싶을 때가 있지 않은가?

폴더는 Flat하게 만들자

어떻게 구성해야할 것인지에 대해서 다음과 같은 결정을 내렸다. 개인적인 결정이므로 더 좋은 방법이 있을 수 있다.

  • 폴더는 2020-11-25 와 같이 날짜 형식으로만 만든다. 이하 서브폴더는 만들지 않는다.
  • 하루에 다수의 포스팅을 하는 경우는 극히 드물기 때문에 각 포스트를 위한 폴더는 만들지 않는다.
  • hello-world.md 와 같이 제목을 가진 markdown파일을 만든다. (index.md는 엄청난 마이그래이션 작업을 유발할 수 있다.)
  • slug 는 YYYY/MM/DD/title로 구성한다.

GraphQL 데이터 살펴보기

드디어 코딩 타임이다. 위에서 결정한 구성을 구현하기 위해서는 slug를 생성하는 코드를 만들어 넣어야 한다. gatsby-node.js를 살짝 훔쳐보고 slug에 대한 정보를 얻으 은 뒤 GraphQL 콘솔에 접속해서 slug를 조회해 본다.

쿼리:

query MyQuery {
  allMarkdownRemark {
    edges {
      node {
        fields {
          slug
        }
      }
    }
  }
}

결과:

{
  "data": {
    "allMarkdownRemark": {
      "edges": [
        {
          "node": {
            "fields": {
              "slug": "/2020-11-25/deep-javascript/"
            }
          }
        },
        {
          "node": {
            "fields": {
              "slug": "/2020-11-23/new-beginnings/"
            }
          }
        },
      ]
    }
  },
  "extensions": {}
}

graphQL에 대한 지식이 전무하기에 약간 이해가 되지 않는 부분들이 있지만 미루어 짐작해 볼 수 있을 정도로 깔끔하다. 그래프라는 자료 구조를 생각해 볼때 아마도 allMarkdownRemark라는 node에 연결된 노드들이 각 마크다운 파일에 해당하는 노드일 것이다. 그 자식 노드들은 그래프에서 간선을 나타내는 용어인 edge를 모아 놓은 edges 배열에 각각 연결이 되어 있을 것이다. 그래서 edges { node } 라는 쿼리가 있는 것이 아닐까?

사실이든 아니든 slug는 nodefields 안에 있고 그 것을 조작해주면 된다.

Gatsby-node.js 에 코드 추가

사실상 코딩은 replace 함수를 포함한 한줄의 추가로 끝났다(머쓱). Gatsby에서 제공하는 몇몇 API들의 역할을 파악했다는 것에 더 큰 의의를 두어야겠다.

// 새로운 node가 생성될 때 호출되는 함수. node를 extend하기 위해서 이 함수를 구현해야한다.
exports.onCreateNode = ({ node, actions, getNode }) => {
  // Gatsby는 내부적으로 redux를 통해서 상태를 관리하므로 action을 dispatch해서 상태를 변경한다.
  // actions에 action들이 들어 있고 createNodeField은 그들 중 하나. node를 확장하기 위해 사용한다.
  const { createNodeField } = actions

  // MarkdownRemark에 해당하는 노드 일때만 field를 추가한다.
  if (node.internal.type === `MarkdownRemark`) {
    // createFilePath는 gatsby-source-filesystem 플러그인이 제공하는 함수로 file path를 URL로 변경해준다.
    // 이 함수에서 나온 결과물이 slug가 된다. 그 것을 YYYY/MM/DD/title 형태로 바꿔준다.
    const filePath = createFilePath({ node, getNode })

    // NNNN-NN-NN가 등장하는 첫 패턴을 찾은 후, NNNN/NN/NN으로 변경한다.
    const value = filePath.replace(/\/(\d{4})-(\d{2})-(\d{2})\//, "/$1/$2/$3/")

    // node에 추가되는 slug은 이 함수에 의해 fields에 추가된다.
    createNodeField({
      name: slug,
      node,
      value,
    })
  }
}

이제 /2020-11-23/new-beginnings.md로 생성된 문서를 /2020/11/23/new-beginnings 로 접근이 가능하다.

끝.