1. App Router로 변경해야 하는 이유
Next.js 14가 나오면서 공식 문서에서도 App Router를 권장했다. 기존 pages/ 폴더 기반의 Pages Router는 점차 유지보수 대상에서 밀려나고 있는 상황이었다.
이번 기회에 프로젝트를 App Router로 마이그레이션하면서 변경해야 할 부분을 정리했다.
주요 장점은 다음과 같았다.
- 서버 컴포넌트 지원으로 클라이언트에서 불필요한 JS 실행을 줄일 수 있었다.
- 레이아웃을 더 직관적으로 관리할 수 있었다.
- 파일 기반 라우팅이 더욱 강력해졌다.
- TypeScript 없이도 사용 가능했다.
대부분의 자료가 TypeScript 기반이었지만, 자바스크립트만으로도 App Router를 충분히 활용할 수 있었다.
이번 마이그레이션 과정도 TS 없이 JavaScript만으로 진행했다.
2. 기존 프로젝트 구조 (Pages Router)
이전 프로젝트는 기존의 Pages Router 방식으로 구성되어 있었다.
/pages
├─ index.js
├─ _app.js
├─ components/
│ ├─ Header.js
│ ├─ HeroSection.js
│ ├─ AboutSection.js
│ ├─ PhotosSection.js
│ ├─ MixSection.js
│ ├─ FixedFooter.js
│ ├─ BootScreen.js
/styles
└─ globals.css
기본적으로 pages/index.js가 메인 페이지였고,
_app.js에서 전역적인 스타일과 레이아웃을 관리했다.
3. App Router로 마이그레이션하기
Next.js 14에서는 App Router가 기본값이었고, app/ 폴더를 만들어야 했다.
기존 pages/ 폴더를 삭제하고 app/ 폴더 기반으로 프로젝트를 변경했다.
/app
├─ layout.js ← 기존 `_app.js` 역할
├─ page.js ← 기존 `index.js` 역할
├─ components/
│ ├─ Header.js
│ ├─ HeroSection.js
│ ├─ AboutSection.js
│ ├─ PhotosSection.js
│ ├─ MixSection.js
│ ├─ FixedFooter.js
│ ├─ BootScreen.js
/styles
└─ globals.css
3.1 _app.js → layout.js
_app.js에서 하던 역할을 layout.js로 옮겼다.
모든 페이지에 적용될 전역 레이아웃이 필요했기 때문에, 기존 _app.js 내용을 수정해 layout.js에 넣었다.
// app/layout.js
import '../styles/globals.css';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
이전에는 _app.js에서 <Component {...pageProps} />로 페이지를 렌더링했지만,
App Router에서는 {children}을 통해 하위 페이지를 감싸는 방식으로 바뀌었다.
3.2 index.js → page.js
기존 index.js를 page.js로 바꿔 app/ 폴더 안에 넣었다.
// app/page.js
import React from "react";
import Header from "./components/Header";
import HeroSection from "./components/HeroSection";
import AboutSection from "./components/AboutSection";
import PhotosSection from "./components/PhotosSection";
import MixSection from "./components/MixSection";
import FixedFooter from "./components/FixedFooter";
export default function Home() {
return (
<>
<Header />
<HeroSection />
<AboutSection />
<PhotosSection />
<MixSection />
<FixedFooter />
</>
);
}
Next.js 14에서는 page.js가 각 폴더의 메인 엔트리 역할을 했다.
따라서 기존 index.js 파일을 page.js로 이름만 바꿔 사용하면 됐다.
3.3 "use client" 선언
App Router에서는 기본적으로 서버 컴포넌트가 기본이었다.
그런데 useState, useEffect 같은 클라이언트 훅을 사용하는 컴포넌트는 클라이언트 컴포넌트로 지정해야 했다.
예를 들어, HeroSection.js에서 useEffect를 사용하고 있었다.
따라서, 최상단에 "use client";를 추가했다.
// app/components/HeroSection.js
"use client";
import React, { useEffect } from "react";
export default function HeroSection() {
useEffect(() => {
console.log("Hero Section loaded");
}, []);
return <div>Hero Section</div>;
}
이 선언이 없으면 Next.js가 이 컴포넌트를 서버에서 렌더링하려고 시도해 오류가 발생했다.
4. 마이그레이션 후 문제 해결
4.1 next/head 대신 metadata 사용
기존 next/head를 사용해 <title> 같은 정보를 설정했지만,
App Router에서는 layout.js나 page.js에서 metadata 속성으로 SEO 정보를 설정해야 했다.
// app/layout.js
export const metadata = {
title: "My Portfolio",
description: "My portfolio built with Next.js 14 App Router",
};
이렇게 하면 Next.js가 자동으로 <head> 태그를 구성해줬다.
4.2 Swiper 관련 문제
Swiper가 클라이언트 전용 라이브러리라서, SSR 없이 동적으로 import해야 했다.
Next.js 14에서는 dynamic()을 사용해 ssr: false 옵션을 설정했다.
import dynamic from "next/dynamic";
const Swiper = dynamic(() => import("swiper/react").then((mod) => mod.Swiper), {
ssr: false,
});
const SwiperSlide = dynamic(() => import("swiper/react").then((mod) => mod.SwiperSlide), {
ssr: false,
});
이렇게 하면 Next.js가 서버에서 Swiper를 렌더링하지 않도록 방지할 수 있었다.
오늘 정리
- pages/ 폴더를 app/ 폴더로 변환했다.
- _app.js는 layout.js로, index.js는 page.js로 변경했다.
- 클라이언트 컴포넌트는 "use client"를 선언했다.
- next/head 대신 metadata를 사용했다.
- Swiper 같은 클라이언트 전용 라이브러리는 dynamic()을 활용해 ssr: false로 처리했다.
회고
App Router는 처음에는 낯설었지만, 기존 구조를 유지하면서도 더 직관적인 방식으로 바뀌었다. TypeScript 없이 JavaScript로도 충분히 사용할 수 있었고, Next.js 14에서 추천하는 방식을 따라가면서 코드를 정리할 수 있었다.
Next.js의 변화가 빠른 만큼, 앞으로도 꾸준히 최신 기능을 학습해 나가야 할 것 같다.
'Next.js' 카테고리의 다른 글
캐시 활용으로 누락된 서버데이터 처리 (0) | 2025.03.01 |
---|---|
React : useMemo로 성능 최적화하기 (0) | 2025.02.26 |
Next.js에서 GSAP 최적화하기: 중앙집중화로 성능 개선 (0) | 2025.01.22 |
Next.js 15에서 발생한 Hydration 오류와 해결 과정 (0) | 2025.01.20 |
Next.js // GSAP ScrollTrigger 적용하면서 겪은 문제 해결 (0) | 2025.01.17 |