Gatsby.js는 JAM 스택을 기반으로 한 정적 사이트 생성기이다. 정적 사이트란 서버에 존재하는 데이터를 변화시키는 일 없이 그대로 사용자에게 보여주는, 고정된 웹 사이트 형식을 말한다.
복잡한 API 통신이나 백엔드를 설계해야 할 일이 적으므로 순수 자바스크립트나 리액트 정도만으로도 만들 수 있겠지만, 그럼에도 Gatsby를 사용하는 이유는 수많은 유용한 기능 때문일 것이다.
그 중 하나가 동적 페이지 생성이다. 이 기능이 없다면 우리는 주어진 데이터마다 각각의 페이지를 일일히 생성해야 한다. 그 과정에서 불필요하게 중복된 코드를 작성하게 되며 페이지 경로나 링크도 전부 하나씩 관리해야 하기 때문에 매우 불편하다. 하지만 정해진 양식에 따라 자동으로 페이지를 생성해주는 기능이 있다면 매우 편할 것이다.
동적 페이지를 만드는 첫 번째 방법은 Gatsby의 File System Route API인 collection routes를 사용하는 것이다. 동적 페이지의 템플릿으로 사용하고 싶은 컴포넌트를 적절한 경로에 넣고 파일명을 {node.field}.tsx
형식으로 작성한다. 해당 파일이 존재하는 경로와 node 타입의 field 값에 따라 자동으로 페이지가 생성된다.
예를 들어 src/pages/dictionary/{mdx.slug}.tsx
가 있으면 mdx 타입의 노드 전체를 대상으로 해당 노드 인터페이스에 있는 slug 값에 따라 URL과 페이지가 생성된다.
gatsby-plugin-mdx
는 mdx 타입의 노드마다 자동으로 경로와 파일명을 따서 slug라는 필드를 생성한다. URL로 사용하기에 적절한 값이다.Gatsby는 이 템플릿으로 만들어진 각각의 페이지 컴포넌트마다, 사용된 노드의 id
값과 field
값을 제공한다. 이것은 페이지 컴포넌트에서 쿼리를 요청할 때 쿼리 변수로 사용될 수 있다.
// src/pages/blog/{mdx.slug}.js
const BlogPost = ({ data }) => {
return (
<Layout pageTitle={data.mdx.frontmatter.title}>
<p>{data.mdx.frontmatter.date}</p>
<MDXRenderer>
{data.mdx.body}
</MDXRenderer>
</Layout>
)
}
export const query = graphql`
query ($id: String) {
mdx(id: {eq: $id}) {
frontmatter {
title
date(formatString: "MMMM D, YYYY")
}
body
}
}
`
위 코드에서는 id 값을 쿼리 변수로 사용하여 페이지를 만드는 데 쓰인 특정 mdx 노드의 데이터를 가져와 컴포넌트에 렌더링하고 있다.
만약 gatsby-plugin-mdx
를 사용하지 않거나, 플러그인이 자동으로 생성한 slug 값의 형식이 마음에 들지 않는다면 다른 field를 사용하거나 노드마다 사용자 정의 field를 넣어줄 수 있다.
onCreateNode는 Gatsby 프로젝트가 빌드되는 과정 중 노드가 생성될 때 실행된다.
// gatsby-node.js
const slugify = require('slugify');
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === `Mdx`) {
const value = slugify(node.frontmatter.title, { lower: true });
createNodeField({ node, name: 'slug', value });
}
};
위 코드에서는 Mdx 노드의 title 값을 slugify 라이브러리로 변환하여 slug라는 이름으로 넣어주었다. 'field' 필드 아래에 생성된다.
collection routes는 굉장히 편리하지만 보다 디테일한 정의를 위해서는 직접 페이지 생성 함수를 정의해야 한다. 위 코드에서는 모든 mdx 노드를 대상으로 페이지가 생성되기 때문에, 특정 노드만 대상에 포함하고 싶은 상황이다. 이럴 때 createPages
, createPage
API를 사용할 수 있다.
// gatsby-node.js
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions;
// category 값이 'dictionary'인 mdx 노드만 쿼리
const query = await graphql(`
{
allMdx(
filter: { frontmatter: { category: { eq: "dictionary" } } }
sort: { order: DESC, fields: frontmatter___title }
) {
edges {
node {
fields {
slug
}
}
}
}
}
`);
if (query.errors) {
reporter.panicOnBuild(`Error`);
return;
}
// 템플릿 컴포넌트의 절대경로
const TemplateComponent = path.resolve(
__dirname,
'src/components/main/DictionaryTemplate.tsx',
);
query.data.allMdx.edges.forEach(({
node: {
fields: { slug },
},
}) =>
createPage({
path: `/dictionary/${slugs}`,
component: TemplateComponent,
context: { slug },
}));
};
Node API의 일종인 createPages
는 매개변수로 actions
, graphql
(GraphQL API)을 받는다. actions는 Gatsby에서 내부적으로 사용되는 상태를 관리하기 위한 함수의 집합이다. 여기에 포함된 createPage
라는 함수는 생성할 페이지 URL과 템플릿 컴포넌트의 경로, 그리고 context
를 인수로 받아 새로운 페이지를 생성한다.
위 코드에서는 createPages
내부에서 원하는 형식의 노드만 쿼리한 후, 각 노드를 대상으로 직접 createPage
함수를 호출하며 페이지를 생성하고 있다.
이때 context
객체의 값은 (첫 번째 방법에서 그랬던 것과 비슷하게) 템플릿 컴포넌트의 props 또는 페이지 쿼리의 쿼리 변수로 받아 사용할 수 있다.
참고 사이트