nextjs로 이전 했습니다.

9 days ago

1. 왜 Gatsby에서 Next.js로 옮겼을까?

1) Gatsby의 장점과 한계

2) Next.js의 주요 장점

따라서 다양한 렌더링 옵션을 활용해 유연하고 효율적인 블로그를 만들어보고자 Next.js를 선택했습니다.

2. next-mdx-remote vs MDX 플러그인

1) MDX란?

MDX는 Markdown 문법에 React 컴포넌트를 섞어 쓸 수 있게 해주는 포맷입니다. 기존에도 Gatsby MDX 플러그인을 활용하여 블로그 포스트를 작성할 수 있었지만, Next.js에서 MDX를 사용하기 위해선 'next-mdx-remote'나 '@next/mdx' 같은 패키지를 도입하게 됩니다.

2) next-mdx-remote의 특징

저는 블로그 포스트를 각 마크다운(MDX) 파일로 관리하고, 빌드 시점(혹은 ISR) 또는 요청 시점(SSR)에 해당 MDX 파일을 파싱해주도록 설정했습니다. 이를 통해 새로운 글을 작성하거나 수정할 때의 프로세스를 간소화할 수 있었습니다.


3. UI 라이브러리: Chakra UI에서 MUI로 옮긴 이유

1) Chakra UI의 장점과 MUI 선택 배경

2) MUI를 적용하며 느낀 차이점

결국, 다양한 컴포넌트, 풍부한 Material 생태계, 그리고 디자인 표준성 때문에 MUI를 선택했습니다.

4. 마이그레이션 주요 단계

1) Next.js 프로젝트 초기 설정

  1. Next.js 설치
    bash
    1npx create-next-app my-blog
  2. 타입스크립트 설정(선택사항)
    bash
    1touch tsconfig.json
    2npm install --save-dev typescript @types/react @types/node
  3. 폴더 구조 설계
    • 'app' 폴더: 메인 페이지, 블로그 리스트, 각종 라우트 관리.
    • 'components' 폴더: 재사용 가능한 컴포넌트 보관.
    • 'libraries' 폴더: MDX 파싱, 데이터 처리 로직 분리.
    • 'public' 폴더: 이미지, 정적 리소스.

2) next-mdx-remote 설치 및 설정

  1. 의존성 설치
    bash
    1npm install next-mdx-remote gray-matter remark remark-html
  2. MDX 파일 로딩 로직 구현
    • 'libraries/PostManager.ts' 같은 곳에, 아래와 같은 함수를 만들어서 마크다운을 파싱합니다.

      tsx
      1import matter from 'gray-matter';
      2import { serialize } from 'next-mdx-remote/serialize';
      3
      4export async function getPostBySlug(slug: string): Promise<PostData | null> {
      5 ...
      6 // 3) MDX 변환
      7 const source = await serialize(content, {
      8 mdxOptions: {
      9 remarkPlugins: [remarkGfm],
      10 rehypePlugins: [],
      11 format: 'mdx',
      12 },
      13 });
      14 ...
      15}
  3. 페이지에서 MDX 렌더링
    tsx
    1import components from '@/components';
    2import { Box, Container } from '@mui/material';
    3import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote';
    4import React from 'react';
    5
    6import { PostData } from '@/libraries/PostManager';
    7
    8const ArticleContainer = ({ post }: { post: PostData }) => {
    9 const source = post.source as MDXRemoteSerializeResult;
    10 return (
    11 <Container maxWidth='xl'>
    12 <Box py={4}>
    13 <MDXRemote {...source} components={components} />
    14 </Box>
    15 </Container>
    16 );
    17};
    18
    19export default ArticleContainer;

3) MUI 설치 및 테마 설정

  1. 의존성 설치
    bash
    1npm install @mui/material @emotion/react @emotion/styled
  2. 테마 생성 및 적용
    tsx
    1// theme.ts
    2export default responsiveFontSizes(
    3 createTheme({
    4 palette: {
    5 primary: {
    6 light: brand[200],
    7 main: brand[400],
    8 dark: brand[700],
    9 contrastText: brand[50],
    10 },
    11 },
    12 typography: {
    13 fontFamily: robotoFont.style.fontFamily,
    14 },
    15 shadows: customShadows,
    16 components: {
    17 MuiLink: {
    18 styleOverrides: {
    19 root: {
    20 textDecoration: 'none', // 기본적으로 밑줄 제거
    21 color: 'palette.link.main', // 테마 링크 색상 적용
    22 '&:hover': {
    23 color: 'palette.link.hover', // 호버 시 색상 변경
    24 textDecoration: 'underline', // 호버 시 밑줄 추가
    25 },
    26 '&:visited': {
    27 color: 'palette.link.visited', // 방문한 링크 색상
    28 },
    29 },
    30 },
    31 },
    32 },
    33 colorSchemes: {
    34 light: true,
    35 dark: true,
    36 },
    37 cssVariables: {
    38 colorSchemeSelector: 'class',
    39 },
    40 }),
    41);
  3. layout.tsx에서 ThemeProvider 적용
    tsx
    1const RootLayout = async ({
    2 children,
    3}: Readonly<{
    4 children: React.ReactNode;
    5}>) => {
    6 const gaId = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID as string;
    7
    8 return (
    9 <html lang='en' suppressHydrationWarning>
    10 <body>
    11 <InitColorSchemeScript attribute='class' />
    12 <AppRouterCacheProvider>
    13 <ThemeProvider theme={theme} defaultMode={'system'}>
    14 <CssBaseline enableColorScheme />
    15 {children}
    16 </ThemeProvider>
    17 </AppRouterCacheProvider>
    18 </body>
    19 <GoogleAnalytics gaId={gaId} />
    20 </html>
    21 );
    22};

5. 마이그레이션 후 느낀 점

  1. 빌드 시간:
    • Gatsby는 모든 페이지를 빌드 시에 정적으로 생성하기 때문에, 포스트가 많아질수록 빌드 시간이 길어질 수 있었습니다.
    • Next.js는 ISR이나 SSR을 통해 필요한 시점에 생성하거나 갱신할 수 있기 때문에, 상대적으로 빌드 부담이 줄고 배포가 유연해졌습니다.
  2. 유연한 라우팅:
    • Gatsby도 라우팅이 자동화되어 있지만, Next.js의 파일 기반 라우팅은 구조를 직관적으로 파악하기 쉬웠습니다.
    • 동적 라우팅(예: '/post/[slug].tsx')도 쉽게 적용할 수 있어, 블로그 포스트 페이지에 딱 맞았습니다.
  3. MDX 관리:
    • Gatsby에서 플러그인으로 MDX를 사용했을 때는 어느 정도 추상화된 방식으로 동작했지만, Next.js에서는 'next-mdx-remote'를 통해 직접 MDX를 파싱하고 컴포넌트를 주입하는 형태라 세밀한 제어가 가능했습니다.
    • Markdown/MDX 문서를 받아와 렌더링하기가 훨씬 유연해졌고, API 연동, 파일 시스템 연동, CMS 연동 모두 자유도 높아졌습니다.
  4. 스타일링, 디자인:
    • Chakra UI도 훌륭했지만, MUI는 Material Design 기반이라 컴포넌트 종류가 더 방대하고, UI 디자인의 일관성이 유지됩니다.
    • 마이그레이션 시 "디자인 언어" 자체가 달라진다는 점을 감안해 레이아웃, 컬러 시스템 등을 재정의해야 했으나, 그 덕분에 결과물의 완성도와 확장성은 크게 상승했습니다.
  5. 생태계, 커뮤니티:
    • Next.js와 MUI 모두 널리 쓰이는 라이브러리이기 때문에, 문서나 예제, 문제가 발생했을 때 찾을 수 있는 자료가 풍부합니다.
    • 블로그를 넘어 다른 프로젝트와의 호환성을 고려할 때도 MUI와 Next.js 조합은 학습 투자 대비 가치가 높았습니다.

6. 결론 및 향후 계획


마무리하며

이번 마이그레이션 과정은, 기존에 안정적으로 사용하던 Gatsby 블로그에서 Next.js로의 전환이 과연 큰 이점을 줄 수 있을지 의구심을 갖고 시작했지만, 결과적으로 SSR/ISR, MDX 확장성, MUI 생태계라는 세 마리 토끼를 모두 잡는 성공적인 선택이 되었습니다.
앞으로도 프로젝트 환경과 요구사항에 맞춰 최적의 스택을 탐색하고 적용해보는 과정을 즐겨보시길 바랍니다. 혹시 마이그레이션 과정에 궁금한 점이나 피드백이 있다면 언제든지 이슈로 남겨주세요!

감사합니다.


이미지 DRM 구현을 위한 커스텀 이미지 포맷1년의 회고 - 성장과 변화의 시간