Gatsby - 동적으로 페이지 만들기(File system route API) | Dogma
Dogma_blog
Gatsby - 동적으로 페이지 만들기(File system route API)

페이지 쿼리와 템플릿을 활용하여 하나의 템플릿으로 여러 개의 페이지 만들기

2022-01-25 작성


이 글에서는 File system route API를 통해 동적으로 페이지들을 생성하는 과정을 다룬다.
Gatsby tutorial part 6을 기반으로 작성된 포스트이며, mdx 파일들을 기반으로 각각 포스트 페이지 하나씩을 만드는 예시를 기준으로 설명한다.

File system route API

페이지를 렌더링하는 jsx 파일들의 이름을 통해 GraphQL 데이터 층에 접근하여 경로를 만들 수 있도록 도와주는 기능이다.

다음과 같은 모델이 있다고 가정하자. 유효한 코드는 아니고, 단순히 구조를 나타내는 표현이다.

product {
  name
  sku
}

위 모델에 해당하는 예시로,
{ name: 'hamburger', sku: '1122334' },
{ name: 'chicken', sku: '559392' } 와 같은 노드들이 있을 수 있을 것이다.

이때, src/paegs/products/{product.name}.js
/products/hamburger, /products/chicken 페이지를 만들어 낸다.

다른 예시로, src/pages/products/sku/{product.sku}.js
/products/sku/1122334, /products/sku/559392 페이지를 만들어 낸다.

이외에도 File system route API의 다양한 활용법이 있지만 지금 필요한 것은 위에서 설명한 정도로 충분하다.
더 자세한 사용법은 File System Route API 문서를 참조하면 된다.

이제 우리의 프로젝트에서 File system route API를 활용해 보자. 현재 상황과 목표는 다음과 같다.

src/posts 디렉토리에 포스트들이 다음과 같이 각각 mdx 문서로 저장되어 있다.

src/posts
ㄴ hello-world.mdx
ㄴ first-post.mdx
ㄴ another-post.mdx

각각의 파일 이름이 접속 주소가 되도록 만든다.
예로, hello-world.mdx 파일은 /posts/hello-world 경로로 접근할 수 있도록 만든다.

mdx 파일들의 파일명(slug)에 접근하여 그것을 주소로 만들 것이므로, 이들의 GraphQL 상의 모델명인 mdx와 필드 이름인 slug를 조합하면 포스트를 보여주는 컴포넌트의 경로는 src/pages/posts/{mdx.slug}.js 가 될 것이다.

위 경로에 {mdx.slug}.js 파일을 생성하고 다음 코드를 입력한다.
지금은 코드가 동적이지 않고 하드코딩 되어 있지만, 조금 있다가 동적으로 작동하도록 바꿀 것이다.

import * as React from 'react';

const BlogPost = () => {
  return <p>포스트 내용이 여기에 나타날 겁니다(언젠가는).</p>;
};
export default BlogPost;

이제 브라우저에서 localhost:8000/posts/first-post 로 접속해 본다. 위에서 정의한 컴포넌트가 보일 것이다('포스트 내용이 여기에 나타날 겁니다(언젠가는).').

없는 포스트 이름으로 접속해 보아도 좋다. localhost:8000/posts/not-exist 로 접속해 보면 404 페이지를 띄울 것이다. 당연하게도, mdx 파일 중에 not-exist.mdx 라는 파일이 없기 때문에 페이지가 만들어지지 않은 것이다.



페이지 주소가 원하는 대로 잘 생성됨을 확인했다면, 이제 페이지가 하드코딩된 문구가 아닌 각각의 포스트의 내용을 보여주도록 해 보자.

hello-world.mdx 가 다음과 같이 생겼다고 가정하자.

---
title: "Hello, world!"
date: "2022-01-25"
---

Hello, world! 이건 Hello, world! 입니다.

다들 한번씩은 써 봤을 문장이죠.

- hello, world!
- li
- and another li

{mdx.slug}.js페이지 쿼리를 추가해 보자.

import * as React from 'react';

const BlogPost = () => {
  // ... 전과 동일 ...
};

export const query = graphql`
  query ($id: String) {
    mdx(id: { eq: $id }) {
      frontmatter {
        title
        date(formatString: "MMMM D, YYYY")
      }
      body
    }
  }
`;

export default BlogPost;

💡 id값은 모든 노드들에 대해 유일(unique)하므로, allMdx 쿼리가 아닌 mdx 쿼리를 이용하였다.
💡 위의 코드에서 컴포넌트는 아직 수정하지 않았기 때문에 출력값은 전과 같다.

추가된 페이지 쿼리는 id 를 파라미터로 받아, 일치하는 id 값을 가진 노드의 몇몇 필드들을 반환할 것이다.

여기서 몇 가지 의문이 들 것이다.

  • 쿼리의 파라미터는 누가 넘겨주는가?
  • 쿼리의 결과를 컴포넌트에서 어떻게 접근하는가?

파라미터는 누가 넘겨주는가?

이는 File system route API의 역할이다.

템플릿 파일인 {mdx.slug}.js 파일을 이용하여 각각의 .mdx 파일에 대한 페이지를 생성할 때, 그 포스트(.mdx 파일)의 id 값이 페이지 쿼리의 파라미터로 전달된다.

💡 페이지 쿼리로 전달된 파라미터들은 컴포넌트에서도 pageContext 라는 prop을 통해 접근할 수 있다.

import * as React from 'react';
import { graphql } from 'gatsby';

const BlogPost = ({ pageContext }) => {
  return <p>{pageContext.id}</p>;
};

// ... 전과 동일 ...

export default BlogPost;

쿼리의 결과를 컴포넌트에서 어떻게 접근하는가?

쿼리의 결과는 컴포넌트의 data prop으로 접근할 수 있다.

import * as React from 'react';
import { graphql } from 'gatsby';
import { MDXRenderer } from 'gatsby-plugin-mdx';

const BlogPost = ({ data }) => {
  <div>
    <h1>{data.mdx.frontmatter.title}</h1>
    <p>{data.mdx.frontmatter.date} 에 작성</p>
    <MDXRenderer>{data.mdx.body}</MDXRenderer>
  </div>;
};

// ... 전과 동일 ...

export default BlogPost;

이제 포스트의 내용들이 정의한 템플릿에 맞춰 잘 출력됨을 확인할 수 있다.

마무리

정리하면, 위의 코드는 다음과 같이 작동한다.

  • hello-world.mdx를 이용하여 {mdx.slug}.js를 렌더링 시작
  • hello-world.mdx의 GraphQL data layer 상의 id 값을 페이지 쿼리의 파라미터로 넘김
  • 페이지 쿼리 실행 후, 다음과 같은 prop 값과 함께 컴포넌트 렌더링
    • data : 페이지 쿼리의 실행 결과
    • pageContext : 페이지 쿼리가 받은 파라미터들(예시의 경우, id 값)

참고자료

Gatsby tutorial part 6
File System Route API

Source on Github