Next.js로 마이그레이션: Create React App(CRA) 가이드 정리
1. 개요
이 문서는 기존 Create React App(CRA) 프로젝트를 **Next.js(App Router)**로 옮길 때의 공식 절차를, “Draft Mode 정리”와 같은 형식으로 재구성한 것입니다.
가이드의 목표는:
- 최대한 빨리 “동작하는 Next.js 앱”을 만든 뒤
- Next.js의 장점(SSR/SSG/ISR/라우팅/최적화 등)은 점진적으로 채택하는 것
초기 단계에서는 기존 라우터를 당장 바꾸지 않고 SPA 형태로 먼저 올리는 전략을 사용합니다.
2. Step-by-step 마이그레이션
Step 1: Next.js 설치
npm install next@latest
Step 2: Next.js 설정 파일 생성
프로젝트 루트(package.json과 같은 레벨)에 next.config.ts를 만듭니다.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export', // SPA 정적 export 모드
distDir: 'build', // CRA의 build 디렉터리와 맞춤
}
export default nextConfig
output: 'export'는 “정적 export”라서 SSR/API 같은 서버 기능을 사용할 수 없습니다.
서버 기능을 쓰려면 이후에 이 설정을 제거하고 Next.js 서버 모드로 전환합니다.
Step 3: Root Layout 생성
App Router는 Root Layout이 필수입니다.
src/app디렉터리 생성 (또는 루트에app을 두어도 됨)app/layout.tsx생성- CRA의
public/index.html내용을 Layout 구조로 옮김body > div#root부분을<div id="root">{children}</div>로 교체
예시(초기 상태):
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Step 4: Metadata로 <head> 정리
Next.js는 기본 meta charset/viewport 등을 자동 포함하므로, 중복되는 태그는 제거합니다.
또한, 최종적으로는 Metadata API 형태로 선언하는 것이 권장됩니다.
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
favicon.ico,robots.txt같은 파일은app/최상단에 두면 자동으로<head>에 반영됩니다.
Step 5: Styles 적용
- CSS Modules / Global CSS 모두 지원
- 전역 CSS는
app/layout.tsx에서 import
// app/layout.tsx
import '../index.css'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Step 6: 엔트리포인트 페이지 만들기(옵셔널 캐치올)
CRA는 src/index.tsx가 엔트리입니다.
App Router는 라우트 폴더 아래 page.tsx가 엔트리입니다.
초기에는 라우터를 유지하고 SPA처럼 “모든 경로를 한 페이지로” 받아야 하므로,
**옵셔널 캐치올 [[...slug]]**를 사용합니다.
app/[[...slug]]/page.tsx생성- 단일 라우트(
/)만 프리렌더되도록generateStaticParams설정
// app/[[...slug]]/page.tsx
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // 다음 Step에서 교체
}
Step 7: 클라이언트 전용 엔트리포인트 추가
CRA의 App 컴포넌트를 Next.js에서 “완전한 클라이언트 전용(SPA)”로 띄우기 위해
dynamic(..., { ssr: false })를 사용합니다.
// app/[[...slug]]/client.tsx
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
그리고 page.tsx에서 이 컴포넌트를 사용합니다.
// app/[[...slug]]/page.tsx
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
Step 8: 정적 이미지 import 변경
CRA: 이미지 import → “URL 문자열”
Next.js: 이미지 import → “객체”
따라서 기존 <img>를 유지한다면 logo.src를 사용합니다.
import logo from '../public/logo.png'
export default function App() {
return <img src={logo.src} />
}
또는 /public/logo.png는 /logo.png로 접근 가능하므로, URL로 직접 참조하는 방법도 있습니다.
Step 9: 환경 변수 마이그레이션
- CRA에서 클라이언트 노출:
REACT_APP_ - Next.js에서 클라이언트 노출:
NEXT_PUBLIC_
즉, REACT_APP_로 시작하는 환경 변수를 NEXT_PUBLIC_로 변경합니다.
Step 10: package.json 스크립트 변경
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "npx serve@latest ./build"
}
}
그리고 .gitignore에 추가:
.next
next-env.d.ts
이제 실행:
npm run dev
Step 11: CRA 잔여물 정리
삭제/정리 대상 예시:
public/index.htmlsrc/index.tsxsrc/react-app-env.d.tsreportWebVitals관련 코드react-scripts의존성 제거
3. 추가 고려사항(기능 등가 맞추기)
3.1 CRA homepage 필드
CRA에서 서브패스 배포를 위해 homepage를 썼다면, Next.js에서는 basePath로 대응합니다.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
basePath: '/my-subpath',
}
export default nextConfig
3.2 CRA Service Worker / PWA
CRA 기본 서비스 워커를 그대로 가져오기보다, Next.js 방식의 PWA 구성을 검토합니다.
3.3 CRA Proxy(package.json의 proxy)
Next.js에서는 rewrites()로 유사한 프록시 동작을 구성할 수 있습니다.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://your-backend.com/:path*',
},
]
},
}
export default nextConfig
3.4 커스텀 Webpack
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
webpack: (config) => {
// 필요한 변경 적용
return config
},
}
export default nextConfig
커스텀 Webpack을 쓰려면 개발 스크립트에서
next dev --webpack이 필요할 수 있습니다.
3.5 TypeScript 설정
Next.js가 생성하는 next-env.d.ts가 tsconfig.json의 include에 있어야 합니다.
{
"include": ["next-env.d.ts", "app/**/*", "src/**/*"]
}