Frontend/Next

[Next.js] pages에서 app 라우터으로 마이그레이션 (feat. fetch, generateStaticParams)

zeo.y 2023. 7. 31. 18:11
반응형

개요

지난 글과 이어지는 포스팅으로, pages 라우터로 구현했던 마크다운 블로그를 app 라우터로 마이그레이션하는 과정을 남겨두려고 한다. 전체 흐름보다는 변화 포인트에 집중해 보자!

 

 

레이아웃 컴포넌트

  • pages에 있던 _app.tsx_document.tsx는 app의 layout.tsx으로 대체한다.
  • _document.tsx에서 설정했던 styled-componentsapp/layout.tsx로 이동시킨다. (참고. lib/registry 코드 )
  • 컴포넌트로 만들어 두었던 레이아웃도 가져오고, 글로벌 스타일도 추가한다.

 

app/layout.tsx

import Layout from '../components/Layout';
import StyledComponentsRegistry from '../lib/registry';
import '../styles/globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>
          <Layout>{children}</Layout>
        </StyledComponentsRegistry>
      </body>
    </html>
  );
}

 

 

 

홈 페이지 (서버 컴포넌트와 use client)

app 라우팅을 할 경우, 서버 컴포넌트가 디폴트이다. CSR을 활용하려면 'use client' 키워드를 추가해 클라이언트 컴포넌트임을 알려야 한다. 이러한 강제성(?) 때문에 기존 pages/index.tsxapp/page.tsx로 이동했을 때 아래와 같은 에러가 발생했다.

 

문제 1. 클라이언트 컴포넌트에서는 'use client' 써야 함

 

 

이를 해결하기 위해 'use client'를 넣으면, 또 다른 에러가 발생한다.

 

문제 2. API 호출 부분에서 파일을 읽어 들일 수 없음

 

뭐가 문제인지 모르고 한참 삽질을 하다가 원인을 찾아냈다.

바로 styled-components 때문이었다..!

 

결론

  • 홈 페이지에서는 포스트 리스트를 SSG로 불러오기 때문에 서버 컴포넌트로 활용해야 한다. (API 호출)
  • CSS-in-JS인 styled-components는 클라이언트 컴포넌트에서 활용해야 한다. (스타일)

따라서 API 호출 부분과 CSS-in-JS 스타일 직접 적용을 한 파일에서 활용할 수 없다고 판단했다. 그래서 현재 app 폴더 하위는 서버 컴포넌트로, components 폴더 하위는 클라이언트 컴포넌트로 구분해서 작성하기로 했다.

 

아래 코드에서 API 호출 부분은 일단 생략하고 변경된 부분을 살펴보자.

 

pages/index.tsx (Before)

  • 포스트 리스트를 가져오는 API호출과 styled-components가 한 파일에서 공존할 수 있었다.
import { getAllPosts } from '../api';
import { styled } from 'styled-components';

//getStaticProps 있다고 가정

const Home: NextPage<{ posts: Post[] }> = ({ posts }) => {
  return (
    <div>
      <PostBoxes>
        {posts.map((post, index) => (
          <PostBox key={`${post.title}-${index}`} post={post} />
        ))}
      </PostBoxes>
    </div>
  );
};

export default Home;

const PostBoxes = styled.ul`
  display: flex;
  flex-direction: column;
  align-items: center;
`;

 

app/page.tsx (After)

  • API 호출 부분과 styled-components가 공존할 수 없다.
  • 따라서 스타일을 담당하는 부분을 PostBoxes라는 컴포넌트로 뺐다.
  • PostBoxes 컴포넌트에는 'use client' 키워드가 붙는다.
import { getAllPosts } from '../api';
import PostBoxes from '../components/PostBoxes';

const Home: NextPage = () => {
  const posts = getAllPosts() as Post[];
  return <PostBoxes posts={posts} />;
};

export default Home;
//components/PostBoxes.tsx

'use client';

import { styled } from 'styled-components';

const PostBoxes = ({ posts }: PostBoxesProps) => {
  return (
    <Boxes>
      {posts.map((post, index) => (
        <PostBox key={`${post.title}-${index}`} post={post} />
      ))}
    </Boxes>
  );
};

export default PostBoxes;

const Boxes = styled.ul`
  display: flex;
  flex-direction: column;
  align-items: center;
`;

 

API 호출 (fetch)

위에서 변경된 API 호출 부분을 보면, 굉장히 간단한 것을 알 수 있다. app라우터를 사용하면 fetchasync/await을 통해서 서버사이드 패칭을 할 수 있기 때문이다. Next.js가 자동으로 최적화된 비동기 처리를 도와주는 것이다. 따라서 이제 기존의 getServerSidePropsgetStaticProps 같은 메서드는 필요 없고, 위와 같이 간결한 서버 사이드 데이터 패칭 코드를 작성할 수 있는 것이다. (단, 'use client'를 붙인 컴포넌트에서는 불가하다) 

 

SSG로 데이터를 가져오는 홈 페이지의 패칭 부분을 확인해 보자.

 

Before

export const getStaticProps = () => {
  const posts = getAllPosts(['slug', 'title', 'author', 'preview', 'date']);
  return { props: { posts } };
};

const Home = ({ posts }) => {
  return (
  <PostBoxes>
    {posts.map((post, index) => (
      <PostBox key={`${post.title}-${index}`} post={post} />
    ))}
  </PostBoxes>
  );
};

 

After

const Home: NextPage = () => {
  const posts = getAllPosts(['slug', 'title', 'author', 'preview', 'date']) as Post[];
  return <PostBoxes posts={posts} />;
};

 

 

상세 페이지 (generateStaticParams)

상세 페이지에서는 정적 경로를 생성하는 부분을 변경해야 한다. 기존 getStaticPaths로 경로를 생성했던 부분을 generateStaticParams로 바꾸면 된다.

 

pages/[slug].tsx (Before)

export const getStaticPaths = async () => {
  const posts = getAllPosts(['slug']);

  return {
    paths: posts.map((post) => ({ params: { slug: post.slug } })),
    fallback: false,
  };
};

 

app/[slug]/page.tsx (After)

export const generateStaticParams = () => {
  const posts = getAllPosts(['slug']);
  return posts.map((post) => ({ slug: post.slug }));
};

 

 

결과

전체 코드는 깃허브에서 확인할 수 있으며, 배포 링크에서 결과를 볼 수 있다!

 

https://github.com/gayoungyeom/next-ssg

 

GitHub - gayoungyeom/next-ssg: Next.js로 마크다운 블로그 만들기

Next.js로 마크다운 블로그 만들기. Contribute to gayoungyeom/next-ssg development by creating an account on GitHub.

github.com

 

 

Reference

 

 

 

반응형