React+Vite에서 Next.js로 이사하기

들어가며

최근 포트폴리오 기능이 중심이던 웹사이트에 블로그 기능을 추가했다.

그런데 블로그 기능을 추가한 후, 포트폴리오 기능만 있을 때는 크게 신경 쓰지 않았던 CSR(클라이언트 사이드 렌더링)의 한계가 느껴지기 시작했다.

다양한 해결 방법을 고민한 끝에, SSR(서버 사이드 렌더링)을 지원하는 Next.js로 전환하기로 결정했다.

이 글에서는 React + Vite 환경에서 Next.js로 전환을 결심한 이유와 그 과정에서 겪은 경험을 공유하고자 했다.

Next.js로 전환을 결심한 이유

1. SEO(검색 엔진 최적화)에 불리함

기본적으로 React는 CSR 기반의 SPA(Single Page Application)였다.

즉, 브라우저가 처음에 빈 HTML 파일을 받아온 후, JavaScript가 실행되면서 동적으로 화면을 구성하는 방식이었다.

이 방식의 문제는 검색 엔진 크롤러가 JavaScript를 실행하지 못하는 경우, 웹사이트의 콘텐츠를 제대로 인덱싱할 수 없다는 점이었다.

특히 블로그처럼 검색 유입이 중요한 콘텐츠에서는 SSR(서버 사이드 렌더링) 또는 SSG(정적 사이트 생성) 방식이 필수적이었다.

2. 동적으로 Open Graph 생성 불가

Open Graph 태그는 웹사이트가 SNS에 공유될 때 제목, 설명, 썸네일 이미지 등을 미리보기로 표시해주는 프로토콜이었다.

블로그 기능을 추가하면서 각 포스트마다 적절한 Open Graph 태그를 적용할 필요가 있었다.

하지만 CSR(클라이언트 사이드 렌더링) 방식에서는 Open Graph 태그를 동적으로 변경할 수 없고, SNS에서 올바른 미리보기를 생성하지 못하는 문제가 발생했다.

React에서도 이를 해결할 수 있는 몇 가지 방법이 있었지만, 대부분 근본적인 해결책이 아니었고, 사용 가능한 라이브러리들도 유지보수가 중단된 경우가 많았다.

Next.js로 갈아타기

1. 프로젝트 구조 재설계

기존 React 프로젝트에서 Next.js로 전환하면서 가장 큰 변화는 프로젝트 구조의 재설계였다. 특히 Next.js 13 이후 도입된 app 디렉토리 라우팅 방식을 채택했다.

주요 변경사항은 다음과 같았다.

  • src/app 디렉토리를 중심으로 한 라우팅 구조 구현
    • 메인 페이지: app/page.js
    • 블로그: app/blog/page.jsxapp/blog/[id] 동적 라우팅
    • 포트폴리오: app/portfolio/page.jsxapp/portfolio/[id] 동적 라우팅
  • 공통 레이아웃을 app/layout.js로 분리하여 중복 코드를 제거했다.
  • 재사용 가능한 컴포넌트들은 src/components 디렉토리에 별도 관리했다.

2. 스타일링 시스템 마이그레이션

CSS 관리 방식을 Next.js의 모듈 시스템에 맞게 재구성했다.

  • styles 디렉토리를 만들어 components와 pages로 구분하여 관리했다.
  • 모든 CSS 파일을 .module.css 형식으로 변환했다.
  • 전역 스타일은 globals.css에서 관리하도록 구성했다.

3. 데이터베이스 연동

  • Supabase를 데이터베이스로 선택하여 lib/supabase.js에서 클라이언트 초기화 및 설정했다.
  • 서버 컴포넌트에서 직접 데이터베이스 쿼리가 가능하도록 구성했다.

4. 분석 및 모니터링 설정

  • Google Analytics를 위한 별도의 컴포넌트 (GoogleAnalytics.jsx)를 구현했다.

5. 메타데이터 설정

기존 React 환경에서는 react-helmet이나 유사한 라이브러리를 사용해야 했던 메타데이터 관리를 Next.js에서는 더 효율적으로 구현할 수 있었다.

각 레이아웃 파일에서 메타데이터를 다음과 같이 설정했다.

// app/layout.js
export const metadata = {
    title: "joonseo1227",
    openGraph: {
        siteName: "joonseo1227",
        title: "joonseo1227",
        description: "정준서의 홈페이지",
        images: "...",
    },
    twitter: {
        card: "summary_large_image",
        title: "joonseo1227",
        description: "정준서의 홈페이지",
        images: "...",
    },
    icons: {
        shortcut: '/src/app/favicon.ico',
    },
};

동적 라우트에서는 다음과 같이 페이지별로 다른 메타데이터를 생성할 수 있었다.

// app/blog/[id]/layout.jsx
export async function generateMetadata({ params }) {
  const post = await getPost(params.id);  // 블로그 포스트 데이터 가져오기

  return {
    title: post.title,
    description: post.description,
    openGraph: {
      title: post.title,
      description: post.description,
      images: [post.thumbnail],
      type: 'article',
    }
  };
}

이러한 방식으로 메타데이터를 관리함으로써 다음과 같은 이점을 얻을 수 있었다.

  • 각 페이지별로 동적인 SEO 최적화가 가능했다.
  • SNS 공유 시 정확한 미리보기를 제공할 수 있었다.
  • 검색 엔진의 효과적인 크롤링을 지원할 수 있었다.
  • 서버 사이드에서 생성되어 초기 로딩 시 즉시 메타 태그가 반영됐다.

특히 블로그 포스트나 포트폴리오 프로젝트와 같은 동적 콘텐츠에서 각 콘텐츠에 맞는 메타데이터를 자동으로 생성할 수 있게 되어, SEO와 소셜 미디어 공유 최적화를 효과적으로 달성할 수 있었다.

6. 배포 환경 개선

GitHub Pages에서 Vercel로 배포 환경을 전환했다.

기존에는 GitHub Pages에서 직접 Github Actions를 설정하여 사이트를 호스팅했지만, Next.js의 서버 기능을 활용하기 위해 Vercel로 변경했다. 특히 Vercel은 GitHub과 연동하면 자동으로 배포되는 시스템을 제공하기 때문에, 직접 Github Actions를 설정할 필요가 없어 기존보다 훨씬 편리한 배포 환경을 경험할 수 있었다.

전환 과정에서 겪었던 문제와 해결 방법

1. 로컬 폰트 적용 문제

처음에는 Next.js에서 제공하는 next/font 기능을 사용해 폰트를 적용하려 했지만, 예상치 못한 오류가 계속 발생했다.

또한, CSS에서 폰트 경로를 설정할 때 상대 경로 문제로 인해 폰트가 정상적으로 표시되지 않는 오류가 생겼다.

여러 차례 경로를 수정해 봤지만 해결되지 않아, 결국 public 폴더에 폰트 파일을 직접 추가한 후, @font-face를 이용해 적용하는 방식으로 변경했다.

2. 폴더 구조의 차이

React 프로젝트에서는 컴포넌트를 src/components 폴더에 정리하는 방식이 일반적이었지만, Next.js에서는 pages 폴더를 중심으로 파일 기반 라우팅이 이루어지기 때문에 폴더 구조를 새롭게 정리해야 했다.

3. CSS 적용 방식 차이

기존 React 프로젝트에서는 컴포넌트별 CSS 파일을 만들어 스타일을 적용하는 방식을 주로 사용했지만, Next.js에서 module.css라는 새로운 방식을 처음 접하게 됐다.

처음에는 .module.css 파일이 무엇인지조차 몰라서 헤맸고, 기존의 className을 그대로 적용했다가 스타일이 적용되지 않는 문제를 겪었다.

알고 보니 .module.css는 자동으로 클래스 이름을 해싱하여 고유한 스타일을 적용하는 방식이었고, 이를 활용하면 CSS 충돌을 방지할 수 있다는 점이 나름 장점으로 다가왔다.

4. Next.js의 빠른 업데이트 속도

Next.js는 업데이트 속도가 빠른 만큼, 인터넷에 있는 자료가 최신 버전과 맞지 않는 경우가 많았다. 이 때문에 문제를 해결하는 과정에서 예상보다 어려움을 겪기도 했다.

이 과정에서 공식 문서를 수시로 확인하는 습관이 중요하다는 걸 깨달았고, 자연스럽게 공식 문서를 먼저 참고하는 방식으로 접근하게 됐다.

마치며

SEO 최적화, 동적인 Open Graph 태그 적용, 그리고 Vercel을 통한 자동 배포 환경 구축까지, React + Vite에서 Next.js로 전환하는 과정은 새로운 도전이자 값진 경험이었다.

특히, app 디렉토리 기반의 라우팅 시스템을 도입하면서 직관적인 URL 구조와 더 안정적이고 확장 가능한 프로젝트 구조를 구축할 수 있었다.

초반에는 폴더 구조와 스타일링 방식의 차이로 인해 적응하는 데 시간이 걸렸지만, 익숙해지고 나니 서버 사이드 렌더링(SSR)과 정적 사이트 생성(SSG)을 활용하여 더욱 최적화된 웹사이트를 만들 수 있었다.

Next.js는 앞으로도 계속 발전할 것이고 배워야 할 점도 많겠지만, 이번 전환을 통해 웹 개발 방식에 대한 시야가 넓어졌으며, 성능과 유지보수성이 향상된 웹사이트를 구축할 수 있었다.

결과적으로 매우 만족스러운 경험이었다.

댓글 0

댓글을 불러오는 중...

Follow Me