• [React] Gatsby 찍먹해보기 - 설치부터 배포까지

    2022. 12. 20.

    by. 나나 (nykim)

    320x100

     

     

     

    프롤로그

     

    저는 종종 로컬에서 시작해 로컬에서 끝나는 마크업 작업(aka 이메일 템플릿)을 진행하는데요, 

    나중에 파일을 찾으려면 하나씩 파일을 열고 닫아야 하는 수고스러움이 있었습니다.

    "로컬에 흩어진 파일을 한 화면에서 볼 수 있게 정리하고 싶어! 하지만 어떻게?!!!"하고 고민하던 중...

    그레잇한 개츠비를 알게 되어 찍먹해봤습니다.

     

    결론부터 말하자면 (삽질도 많았지만) 좋은 경험이었어요! 

    정적 사이트를 만들 생각인데 바닐라는 좀 그렇고 리액트 기반에서 뚝딱 만들어보고 싶다 하시는 분에게 맞을 것 같아요.

    까먹을 미래를 위해 가볍게 정리해봅니다. 팁은 공식문서가 더 자세하니 이쪽으로 → Gatsby Documentation

     

     

     

     

    What’s Gatsby?

    React 기반의 정적 사이트 생성 프레임워크 (JAM Stack 기반)

     

    JAM🍯...?

    Jamstack is an architecture designed to make the web faster, more secure, and easier to scale.

    잼은 Javascript, Api, Markup 의 앞글자를 하나씩 따왔습니다.

    JAM이란 표현은 Netlify가 제안했다고 하네요

     

    쉽게 말하면, "Javascript & Markup으로 정적 페이지를 표시하고, 필요 시에 API를 통해 데이터를 호출하는 것"이네요.

     

    • 전통적 웹 사이트: 서버에 요청하면 SSR 을 통해 HTML을 만들어 제공
    • SPA : 처음 요청받은 페이지만 SSR 로 제공하고 나머지는 CSR 로 제공
    • JAM Stack: 각 페이지를 HTML로 Pre-Render하여 캐싱 후 CDN에서 제공

          ↪ 사용자에게 화면을 보여주기 위해 준비하는 시간을 단축할 수 있다!는 장점 ⚡️

     

     

    뭘로 마크업할까? (SSG, Static Site Generator)

    https://jamstack.org/generators/

     

    1. JS/React Next.js
    2. Go Hugo
    3. JS/React Gatsby
    4. Ruby Jekyll
    5. JS/Vue Nuxt

     

    그럼 JAM에서 M, 마크업은 뭘로 할 것이냐! 는 자기 마음인데, 저는 React 기반인 Next.js와 Gatsby 중 하나를 고르기로 했습니다.

    둘의 큰 차이점은 SSR 지원 여부로, Next.js와 다르게 Gatsby는 SSR을 지원하지 않습니다.

     

     

    실제 경험하신 분들은 어떨지 검색해보니 이렇게들 말씀하시더라구요 👥

    • 원페이지, 위키, 블로그 등 정적 콘텐츠가 대부분이고 볼륨이 가볍다면 ⇒ Gatsby
    • 동적 데이터 활용도가 많고 콘텐츠 양이 점점 많아질 것으로 예상된다면 ⇒ Next.js

    저는 100% 정적 데이터만 활용해서 앱을 만들 예정이므로 개츠비를 셀렉했습니다. (넥스트는 넥스트 타임에... 👋)

     

     

     


     

     

    다뤄보기

     

    프로젝트 생성

    요구조건: Node.js 18.0.0+

    $ npm install -g gatsby-cli
    $ gatsby new # 터미널로 세팅 가능
    $ cd {프로젝트 폴더}
    $ npm run develop
    
    $ gatsby --version # 5.2.0

     

    세팅할 때 TypeScript나 Sass를 바로 들고 시작할 수 있어 편했습니다.

     

     

    설치가 끝나면 npm run develop 또는 npm run start를 통해 로컬 개발 서버를 실행합니다.

    개발 모드일 때는 정적 파일을 생성하지 않고, gatsby build 실행 시 public/ 폴더에 정적 파일(HTML, CSS, JS)을 만들어 줍니다.

     

     

     

    시작하기

    페이지 생성

    페이지를 만들고 싶다면 그냥 pages/ 폴더 아래에 JSX를 리턴하는 파일을 만들면 됩니다.

    예를 들어 pages/about-us.jsx(또는 tsx)만 만들면 localhost:8000/about-us에서 만나볼 수 있습니다 🤩

    /* src/pages/about-us.tsx */
    
    import React from 'react';
    
    export default function AboutUs(){
      return(
        <h1>ABOUT</h1>
      )
    }
    
    // 이제 `localhost:8000/about-us` 에서 접근 가능

     

    또, 개츠비의 Head API를 통해 title 등 메타데이터를 쉽게 설정할 수 있어요.

    페이지 내에서 Head 컴포넌트를 내보내면 자동으로 처리해 줍니다.

     

    /* src/components/PageMeta.tsx */
    
    import React from 'react';
    
    interface PageMetaProps {
      title: string;
      description?: string;
    }
    
    export default function PageTitle({ title, description = 'Hello World' }: PageMetaProps) {
      return (
        <>
          <title>My Site | {title}</title>
          <meta name="description" content={description} />
        </>
      );
    }
    /* src/pages/about-us.tsx */
    //...
    
    export const Head = () => <PageMeta title="팀을 소개합니다" />;

     

     

     

    페이지 연결하기: Link 컴포넌트

    React Router의 <Link/>와 비슷하게 <Link to={이동할 페이지}/> 형태로 사용합니다.

     

    <Link to="about-us">ABOUT</Link>

     

    재밌는 점, 개츠비의 Link 컴포넌트는 preloading을 제공합니다.

    즉, 링크가 화면에 표시되거나 링크에 마우스오버했을 때 해당 페이지를 미리 불러옵니다.

    따라서 사용자가 클릭했을 때 미리 불러온 페이지를 보여줄 수 있어 빠른 전환이 가능합니다. (WoW)

     

     

     

     

    플러그인 사용하기

    개츠비는 다양한 테마와 플러그인을 제공하는데요, '이런 게 있을까?' 싶어서 검색해보면 필요했던 플러그인이 있어서 신기했어요.

    다만 개츠비가 폭풍성장을 하면서 버전이 쭉쭉 올라가는데 일부 플러그인의 호환성 버전은 그보다 낮아 '어쩌실?'하고 묻는 경우가 종종 있었습니다. (어쩌긴 뭘 어째 설치 고!!)

    peerDependencies  (*peer:동료)
    모듈이 작동되도록 설계된 다른 소프트웨어 라이브러리의 특정 버전이나 버전집합. 브라우저-브라우저 익스텐션 또는 react-redux 간의 관계와 유사하다.

     

     

    개츠비 플러그인은 여기서 살펴볼 수 있어요.

     

    그럼 한번 gatsby-plugin-image 플러그인을 설치해봅니다!

    그외 함께 쓰면 요긴한 gatsby-plugin-sharp 패키지도 같이 설치해 줍니다.

    • gatsby-plugin-sharp: 유동적 크기로 제공하거나 압축하는 등 실질적인 이미지 처리를 다룹니다.
    $ npm install gatsby-plugin-image gatsby-plugin-sharp

     

    gatsby-config.js 파일에서 플러그인을 추가합니다.

    module.exports = {
      plugins: [
        `gatsby-plugin-image`,
        `gatsby-plugin-sharp`,
      ],
    }

     

    이 플러그인은 이미지를 StaticImage 컴포넌트로 제공합니다. Unsplash 이미지를 가져와 src 프롭에 슥 넣어봅니다.

    <StaticImage src="https://images.unsplash.com/photo-1588943211346-0908a1fb0b01?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1035&q=80" alt=""/>

     

     

    짠! 알아서 이미지 최적화를 해줍니다. 원본 파일은 jpeg인데 알아서 webp를 생성해준데다 srcset 설정까지 해줍니다.

    게다가 lazy load 설정까지 척척 👍 placeholder는 기본적으로 dominantColor(가장 많이 추출된 색상값)로 설정되어 있지만 blurred 처리하는 것도 가능합니다.

     

     

    하지만 저한테는 이미지 프로세싱보다 로컬에 있는 파일 정보를 긁어와 보여주는 플러그인이 더 절실했는데요.

    그런 제게 딱 필요한 게 있었으니, gatsby-source-filesystem 플러그인입니다.

    말그대로 파일을 뒤져서(?) 저에게 정적 파일 데이터를 제공해줍니다.

     

    우선 패키지 설치 후 config 파일에 플러그인을 추가합니다.

     

    $ npm install gatsby-source-filesystem
    module.exports = {
      plugins: [
        {
          resolve: `gatsby-source-filesystem`,
          options: {
            name: `data`,
            path: `${__dirname}/static/`
          }
        },
      ],
    }

     

     

    GraphQL로 데이터 가져오기

    그럼 이제 데이터를 어떻게 가져오죠?

    GraphQL이라는 쿼리 언어를 써서 쉽게 데이터를 가져올 수 있어요!

     

    출처: https://www.gatsbyjs.com/docs/tutorial/part-4/

     

    웹 사이트에는 다양한 데이터가 있고 그 데이터들의 Source는 여러 군데로 나누어져 있습니다. (ex. 파일 시스템, DB, CMS, API...)

    우리는 Source Plugin이라는 플러그인을 설치해서 제각각 특정 소스와 통신하게 합니다.

    아까 설치한 gatsby-source-filesystem은 파일 시스템과 통신하는 소스 플러그인인 거죠.

    이렇게 플러그인이 가져온 데이터는 GraphQL Data라는 계층에 추가됩니다.

     

    다음에 우리가 할 일은? GraphQL 쿼리를 던져서 필요한 데이터를 가져오면 됩니다!

     

     

    GraphiQL

     

    npm run start를 했을 때 localhost:8000/___graphql 라는 엔드포인트를 알려주는 걸 볼 수 있습니다.

     

     

     

    각 필드를 탐색하고 클릭만으로 쉽게 쿼리를 작성할 수 있는데다 미리 실행하여 에러를 파악하는 것도 가능합니다 (WoW)22

     

     

     

    config 파일에서 플러그인 옵션을 {path: `${__dirname}/static/`}으로 지정했었죠!

    이 폴더엔 html 파일과 jpeg 파일을 두 개 넣어놨는데 GraphiQL에서 그 파일을 찾아봅니다.

     

    저처럼 외부 파일을 가져와서 표시해야 하는 경우, 루트에 static이란 이름의 폴더를 만들어 사용합니다.
    그러면 빌드 시 static 폴더 안의 파일들은 건들지 않고 그대로 복사해 public에 넣어줍니다.

     

     

    클릭 몇 번 만으로 파일 데이터 정보를 손쉽게 가져왔습니다 🤟

     

    그럼 코드 안에서 실제로 쿼리를 던져 봅니다.

    graphql`` 안에 쿼리를 복사해 붙여넣으면 됩니다. 이때, 필터링을 해서 데이터를 받아올 수도 있어요. 아래는 확장자가 html인 파일만 가져오도록 한 코드입니다. REST API와 다르게 쿼리에 따라 다른 응답을 받아올 수 있다는 게 특징입니다.

     

    import React from 'react';
    import { graphql, PageProps } from 'gatsby';
    import PageMeta from '../components/PageMeta';
    
    export default function Files({ data }: PageProps<Queries.MyFilesQuery>) {
      return (
        <>
          {data.allFile.nodes.map((file, index) => (
            <div key={index}>{file.name}</div>
          ))}
        </>
      );
    }
    
    export const query = () => graphql`
      query MyFiles {
        allFile(filter: { ext: { eq: ".html" } }) {
          nodes {
            name
          }
        }
      }
    `;
    
    export const Head = () => <PageMeta title="파일 목록" />;

     

    config에서 graphqlTypegen:true (디폴트 값)로 설정되어 있는 경우 쿼리 타입을 자동으로 생성해 줍니다.

    gatsby-types.d.ts 파일 안에 알아서 눈치껏 생성해 주기에 제네릭으로 가져와 쓸 수 있습니다.

     

     

    VS CODE에서 GraphQL이 제대로 표시되지 않는다면 익스텐션을 설치합니다.

     

     

     

     

    StaticQuery

     

    페이지가 아닌 컴포넌트에서 GraphQL을 쓰고 싶다면 useStaticQuery를 사용합니다.

    페이지 쿼리와는 다르게 변수를 받아와 쓸 수 없다는 차이점(그래서 "static")이 있지만, 어떤 컴포넌트에서든 쿼리를 사용할 수 있습니다.

     

    import React from 'react';
    import { graphql, useStaticQuery } from 'gatsby';
    import PageMeta from '../components/PageMeta';
    
    export default function Files() {
      const { allFile: { nodes: data } } = useStaticQuery<Queries.getAllFileQuery>(graphql`
        query getAllFile {
          allFile {
            nodes {
              id
              name
              ext
              relativeDirectory
            }
          }
        }
      `);
      return (
        <>
          {data.map((file, index) => (
            <p key={index}>{file.name}</p>
          ))}
        </>
      );
    }
    
    export const Head = () => <PageMeta title="파일 목록" />;

     

     

     

    File System Route로 동적 페이지 만들기

    Gatsby에서 라우트를 설정하는 방법은 pages/ 내에 컴포넌트를 위치시키는 것 말고도,
    File System Route API를 사용하면 GraphQL에서 프로그래밍 방식으로 페이지를 생성하고 클라이언트 전용 경로를 만들 수 있습니다.

     

    예를 들어, pages/{File.relativeDirectory}/{File.name}.tsx 는 디렉토리와 파일명으로 각 페이지를 생성합니다.

    /* src/pages/{File.relativeDirectory}/{File.name}.tsx */
    
    import React from 'react';
    import { graphql } from 'gatsby';
    import PageMeta from '../../components/PageMeta';
    
    interface FilesProps {
      data: Queries.FileInfoQuery;
    }
    
    const File = ({ data: { file } }: FilesProps) => {
      return <h1>{file?.name}</h1>;
    };
    
    export default File;
    
    export const query = () => graphql`
      query FileInfo($name: String!) {
        file(name: { eq: $name }) {
          name
        }
      }
    `;
    
    export const Head = ({ data }: { data: Queries.FileInfoQuery }) => (
      <PageMeta title={`${data.file?.name}`} />
    );

     

     

    coffee/ 폴더 아래 ameriacno와 latte 파일에 맞춰 페이지가 자동으로 생성됐습니다.

    이걸 빌드하게 되면 Gatsby는 각 경로에 있는 페이지를 하나씩 정적으로 만들어 줍니다.

     

    빌드된 모습

     

     

     

    이걸 보니 확실히 블로그 만들 때 좋을 것 같네요 🤩

    마크다운 문법으로 글 작성해서 mdx로 내보내면 받아올 수 있을 것 같은데요?!

    넵, 하지만 그러려면 transformer plugin 설치가 필요합니다.

     

    $ npm install gatsby-plugin-mdx

     

    소스의 데이터를 가져오기 위해 플러그인을 설치했던 것처럼, 데이터 형식을 적합하게 바꿔주는 변환기 플러그인을 설치하면 됩니다. MDX라면 gatsby-plugin-mdx 플러그인이 있어요!

     

    출처: https://www.gatsbyjs.com/docs/tutorial/part-5/

     

    아까 데이터는 GraphQL data 계층에 저장된다고 했었는데요, 데이터 계층 내에서 정보들은 노드(node)라는 최소 단위의 객체에 저장됩니다. 이러한 노드들의 유형은 어떤 source plugin인가에 따라 다릅니다.

    그리고 이 노드의 유형을 변환하는 게 transformer plugin입니다.

     

    gatsby-source-filesystem이 데이터를 File 노드로 생성하고, gatsby-plugin-mdx 플러그인이 확장자가 .mdx인 File 노드들을 MDX 노드로 바꿉니다. 이렇게 데이터를 가져올 때 원하는 구조나 형식으로 바꿔 가져올 수도 있고요.

     

    /* my-post.mdx */
    
    ---
    title: "My First Post"
    date: "2022-12-25"
    slug: "my-first-post"
    ---
    
    메리 크리스마스!
    export const query = () => graphql`
      query BlogPosts {
        allMdx {
          nodes {
            frontmatter {
              title
              date(formatString: "YYYY.MM.DD")
              slug
            }
            excerpt
          }
        }
      }
    `;
    
    const Blog = ({ data }: PageProps<Queries.BlogPostsQuery>) => {
      return (
        <>
        {data.allMdx.nodes.map((file, index) => (
          <article key={index}>
            <Link to={`${file.frontmatter?.slug}`}>
              <h2>{file.frontmatter?.title}</h2>
              <strong>
                {file.frontmatter?.date}
              </strong>
              <p>{file.excerpt}</p>
            </Link>
          </article>
        ))}
        </>
      );
    };

     

    Gatsby로 블로그 만드는 법은 공식 튜토리얼이나 좋은 강좌들이 많기 때문에 생략합니다 😌

     

     

     

    이정도면 제가 원하는 걸 만들기엔 충분했기에 플러그인을 설치해 작업했습니다.

    1. static/ 아래 정적 html 파일을 둔다

    2. File System Route로 새 html 파일이 추가될 때마다 {File.name}으로 경로가 만들어지도록 한다

    3. {File.name} 내에 해당하는 정적 html 파일을 iframe으로 표시하고 관련된 정보를 표시한다.

    4. {File.name} 내에서 파일 다운로드 및 새창열기 클릭 시, 원본 static 파일의 URI로 연결시킨다.

    5. GNB에 allFile 정보를 가져와 맵으로 고이 뿌려준다.

    6. GNB 클릭 시 Link 컴포넌트로 해당 페이지에 연결한다.

    - 끝 -

     

    좌측 GNB 클릭시 해당하는 로컬 파일을 우측에 표시하여 빠른 탐색이 가능해졌습니다 😎

     

     

     

     

     

    gh-pages로 배포하기

     

    배포하려면 우선 빌드해야겠죠! gatsby build를 입력하면 빌드해서 public/ 폴더를 만들어 줍니다.

    프로덕션 빌드를 미리 보고 싶다면 gatsby serve를 실행합니다. 이는 localhost:9000 포트에서 볼 수 있어요.

     

     

    개발이나 빌드 중 에러가 뜬다면 gatsby clean으로 말끔하게 청소해주면 좋습니다.

     

     

    일단 빌드를 하게 되면 Gatsby는 필요한 모든 데이터가 있는 페이지들을 미리 구축하고 컴파일한 사이트를 내놓습니다. 그래서 배포 후에는 SSR이 필요하지 않습니다.

    빌드하는 동안 사이트에 필요한 모든 데이터를 정적 스냅샷과 함께 GraphQL 스키마로 결합됩니다. 빌드 시 이미 데이터가 수집되었기 때문에 네트워크를 통해 이동할 필요가 없고, 모든 데이터가 데이터 레이어에 결합되기 때문에 동시에 여러 소스를 쿼리하는 것도 가능합니다. (출처)

     

     

     

    이렇게 빌드된 파일을 고이 들고 배포할 차례입니다.

    저는 깃페이지로 배포하기 위해 gh-pages 패키지를 사용했습니다.

    $ npm i gh-pages

     

    깃헙 레포 설정 화면에 들어가 Pages에서 배포용 브랜치를 선택합니다.

     

     

     

    package.json 에서 배포용 브랜치에 배포되도록 설정하면 끝!

    "scripts": {
        "deploy": "gatsby build && gh-pages -d public -b deploy",
    }

     

     

     

    유의할 점

     

    작성한 코드 내용 중, window.location을 활용해 판단하는 구문이 있었는데요.

    개발 모드일 때는 정상 동작하지만 빌드를 돌리면 에러가 발생했습니다 🥲

     

     

    이유는 Node.js 환경에서는 window나 document 같이 브라우저 전역을 참조하는 게 불가능하기 때문입니다. (출처)

    gatsby devlop(또는 npm run start)은 런타임이고, gatsby build는 빌드타임입니다.

    빌드타임에는 브라우저를 실행할 수 없으니 당연히 브라우저 API를 호출할 수 없겠죠.

     

    따라서 window 객체를 찾을 수 없다면 방어하는 내용이 필요합니다.

    const isBrowser = typeof window !== "undefined"
    
    if (isBrowser) {
    window.localStorage.getItem("isLoggedIn") === "true"
    }

     

    아니면 루트 페이지에서부터 location 객체를 자식 컴포넌트로 계속 보낼 수도 있습니다.

     

    /* src/pages/index.tsx */
    
    const IndexPage = ({ location }: { location: Location }) => {
      return <PageLayout location={location}/>;
    };

     

     

    만약 개발모드에서 SSR과 관련해 경고가 필요하다면 config에서 설정하면 됩니다.

     

    module.exports = {
      flags: {
        DEV_SSR: true
      },
      plugins: [...]
    }

     

     

     


     

     

    에필로그

     

    SSG(not 마트)를 짧게 찍먹해보았습니다만 음 맛있네요!

    정적 사이트를 쉽게 구축할 수 있다는 점에서 퍼블리셔에게도 좋은 선택지가 될 수 있을 것 같아요.

    방치해둔지 n년째인 포트폴리오 사이트도 개츠비를 활용해 업뎃해볼까 고려하고 있습니다 😌

    그레잇한 개츠비네요!

     

    생각해보니 위대한 건... 개츠비의 얼굴인가...?

     

    728x90

    댓글