[Next.js] pages에서 app 라우터으로 마이그레이션 (feat. fetch, generateStaticParams)
개요
지난 글과 이어지는 포스팅으로, pages
라우터로 구현했던 마크다운 블로그를 app
라우터로 마이그레이션하는 과정을 남겨두려고 한다. 전체 흐름보다는 변화 포인트에 집중해 보자!
레이아웃 컴포넌트
- pages에 있던
_app.tsx
와_document.tsx
는 app의layout.tsx
으로 대체한다. _document.tsx
에서 설정했던styled-components
도app/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.tsx
를 app/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라우터를 사용하면 fetch
와 async/await
을 통해서 서버사이드 패칭을 할 수 있기 때문이다. Next.js가 자동으로 최적화된 비동기 처리를 도와주는 것이다. 따라서 이제 기존의 getServerSideProps
와 getStaticProps
같은 메서드는 필요 없고, 위와 같이 간결한 서버 사이드 데이터 패칭 코드를 작성할 수 있는 것이다. (단, '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
Reference
- https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app
- https://nextjs.org/docs/app/building-your-application/styling/css-in-js#styled-components
- https://nextjs.org/docs/app/building-your-application/data-fetching/fetching
- https://nextjs.org/docs/app/api-reference/functions/generate-static-params