2025. 4. 2. 16:43ㆍFrameworks/React
Next.js 확장과 차이점
Next.js란 무엇인가요?
Next.js는 React 기반의 풀스택 웹 프레임워크로, 서버 사이드 렌더링(SSR), 정적 사이트 생성(SSG), API 라우트 등 다양한 기능을 제공합니다.
React vs Next.js
React의 특징
- 클라이언트 사이드 렌더링(CSR) 기반
- 단일 페이지 애플리케이션(SPA) 구축에 최적화
- 라우팅, 데이터 페칭 등을 위한 추가 라이브러리 필요
- 빌드 및 번들링을 위한 설정 필요
Next.js의 확장 기능
- 서버 사이드 렌더링(SSR) 내장
- 정적 사이트 생성(SSG) 지원
- 파일 기반 라우팅 시스템
- API 라우트 내장
- 이미지, 폰트 최적화
- 자동 코드 분할
- 설정 없이 바로 사용 가능
Next.js 시작하기
설치 및 프로젝트 생성
# 새 프로젝트 생성
npx create-next-app@latest my-next-app
# TypeScript 템플릿 사용
npx create-next-app@latest --typescript my-next-app
# 개발 서버 실행
cd my-next-app
npm run dev
기본 파일 구조
my-next-app/
├── app/ # App Router (Next.js 13+)
│ ├── page.tsx # 홈페이지
│ ├── layout.tsx # 루트 레이아웃
│ ├── about/ # /about 라우트
│ │ └── page.tsx
│ └── api/ # API 라우트
│ └── hello/
│ └── route.ts
├── public/ # 정적 파일
├── components/ # 재사용 컴포넌트
├── next.config.js # Next.js 설정
├── package.json # 패키지 정보
└── tsconfig.json # TypeScript 설정
라우팅 시스템
페이지와 라우팅
Next.js는 파일 시스템 기반 라우팅을 사용합니다.
app/
├── page.tsx # / (홈)
├── about/
│ └── page.tsx # /about
├── blog/
│ ├── page.tsx # /blog (블로그 목록)
│ └── [slug]/ # 동적 라우트
│ └── page.tsx # /blog/:slug (개별 블로그)
└── dashboard/
└── [...slug]/ # /dashboard/* (모든 하위 경로)
└── page.tsx
동적 라우트
파일명에 대괄호를 사용하여 동적 라우트를 생성할 수 있습니다.
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }) {
return <div>블로그 제목: {params.slug}</div>;
}
라우트 그룹
괄호로 폴더 이름을 감싸서 URL 구조에 영향을 주지 않는 라우트 그룹을 만들 수 있습니다.
app/
├── (marketing)/ # URL에 포함되지 않음
│ ├── about/
│ │ └── page.tsx # /about
│ └── contact/
│ └── page.tsx # /contact
└── (shop)/ # URL에 포함되지 않음
├── products/
│ └── page.tsx # /products
└── categories/
└── page.tsx # /categories
링크 및 네비게이션
Next.js의 Link 컴포넌트를 사용하여 클라이언트 사이드 네비게이션을 구현할 수 있습니다.
import Link from 'next/link';
export default function Navigation() {
return (
<nav>
<Link href="/">홈</Link>
<Link href="/about">소개</Link>
<Link href="/blog">블로그</Link>
<Link href="/blog/hello-world">블로그 글</Link>
<Link href="/products?category=electronics">전자제품</Link>
</nav>
);
}
데이터 가져오기
서버 컴포넌트와 데이터 페칭
Next.js 13부터는 React 서버 컴포넌트를 사용합니다. 서버 컴포넌트에서는 async/await를 직접 사용할 수 있습니다.
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return (
<div>
<h1>사용자 목록</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
정적 데이터 생성 (SSG)
빌드 시점에 데이터를 가져와 정적 페이지를 생성합니다.
// app/posts/[id]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://jsonplaceholder.typicode.com/posts').then(res => res.json());
return posts.slice(0, 10).map(post => ({
id: post.id.toString(),
}));
}
async function getPost(id) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
return res.json();
}
export default async function Post({ params }) {
const post = await getPost(params.id);
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
증분 정적 재생성 (ISR)
일정 시간마다 정적 페이지를 다시 생성합니다.
// app/dashboard/page.tsx
async function getDashboardData() {
const res = await fetch('https://api.example.com/dashboard', {
cache: 'no-store' // 항상 새로운 데이터 가져오기
});
return res.json();
}
export default async function Dashboard() {
const data = await getDashboardData();
return (
<div>
<h1>대시보드</h1>
<p>현재 사용자 수: {data.userCount}</p>
<p>총 판매액: {data.totalSales}원</p>
</div>
);
}
동적 데이터 가져오기 (SSR)
요청 시점에 데이터를 가져와 서버에서 렌더링합니다.
// app/dashboard/page.tsx
async function getDashboardData() {
const res = await fetch('https://api.example.com/dashboard', {
cache: 'no-store' // 항상 새로운 데이터 가져오기
});
return res.json();
}
export default async function Dashboard() {
const data = await getDashboardData();
return (
<div>
<h1>대시보드</h1>
<p>현재 사용자 수: {data.userCount}</p>
<p>총 판매액: {data.totalSales}원</p>
</div>
);
}
API 라우트
Next.js는 서버 API 엔드포인트를 쉽게 만들 수 있게 해줍니다.
기본 API 라우트
// app/api/hello/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({ message: '안녕하세요!' });
}
export async function POST(request) {
const body = await request.json();
return NextResponse.json({ message: `안녕하세요, ${body.name}님!` });
}
동적 API 라우트
// app/api/users/[id]/route.ts
import { NextResponse } from 'next/server';
export async function GET(request, { params }) {
const id = params.id;
// 데이터베이스에서 사용자 정보 가져오기
const user = await fetchUserFromDatabase(id);
if (!user) {
return NextResponse.json(
{ error: '사용자를 찾을 수 없습니다' },
{ status: 404 }
);
}
return NextResponse.json(user);
}
export async function PUT(request, { params }) {
const id = params.id;
const body = await request.json();
// 사용자 정보 업데이트
const updatedUser = await updateUserInDatabase(id, body);
return NextResponse.json(updatedUser);
}
export async function DELETE(request, { params }) {
const id = params.id;
// 사용자 삭제
await deleteUserFromDatabase(id);
return NextResponse.json({ message: '사용자가 삭제되었습니다' });
}
미들웨어와 인증
미들웨어 설정
// middleware.ts (프로젝트 루트에 위치)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const currentUser = request.cookies.get('currentUser');
// 관리자 페이지에 접근 시 로그인 확인
if (request.nextUrl.pathname.startsWith('/admin')) {
if (!currentUser) {
return NextResponse.redirect(new URL('/login', request.url));
}
// 추가 권한 확인 가능
const userData = JSON.parse(currentUser.value);
if (userData.role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
}
return NextResponse.next();
}
// 특정 경로에만 미들웨어 적용
export const config = {
matcher: ['/admin/:path*', '/dashboard/:path*'],
};
인증 구현하기
Next.js는 다양한 인증 방법을 지원합니다. NextAuth.js(현재는 Auth.js)는 Next.js 애플리케이션에서 인증을 쉽게 구현할 수 있게 해주는 라이브러리입니다.
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import GoogleProvider from 'next-auth/providers/google';
const handler = NextAuth({
providers: [
// 자격 증명 기반 로그인
CredentialsProvider({
name: '이메일/비밀번호',
credentials: {
email: { label: '이메일', type: 'email' },
password: { label: '비밀번호', type: 'password' }
},
async authorize(credentials) {
// 데이터베이스에서 사용자 확인 로직
const user = await verifyCredentials(credentials.email, credentials.password);
if (user) {
return user;
} else {
return null;
}
}
}),
// 소셜 로그인
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET
})
],
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30일
},
callbacks: {
async session({ session, token }) {
// 세션에 추가 정보 넣기
session.user.id = token.sub;
return session;
}
},
pages: {
signIn: '/login',
error: '/auth/error',
}
});
export { handler as GET, handler as POST };
클라이언트 컴포넌트에서 세션 사용하기
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
export default function AuthStatus() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <div>세션 로딩 중...</div>;
}
if (session) {
return (
<div>
<p>로그인됨: {session.user.email}</p>
<button onClick={() => signOut()}>로그아웃</button>
</div>
);
}
return (
<div>
<p>로그인되지 않음</p>
<button onClick={() => signIn()}>로그인</button>
</div>
);
}
스타일링
Next.js는 다양한 스타일링 방법을 지원합니다.
CSS 모듈
/* Button.module.css */
.button {
padding: 10px 20px;
background-color: #0070f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button:hover {
background-color: #0055b3;
}
// Button.tsx
import styles from './Button.module.css';
export default function Button({ children, onClick }) {
return (
<button className={styles.button} onClick={onClick}>
{children}
</button>
);
}
Tailwind CSS
Next.js 프로젝트에 Tailwind CSS를 쉽게 통합할 수 있습니다.
// app/page.tsx
export default function Home() {
return (
<div className="flex min-h-screen flex-col items-center justify-center p-24">
<h1 className="text-4xl font-bold text-center mb-4">
Next.js + Tailwind CSS
</h1>
<p className="text-gray-600 text-center max-w-2xl">
Next.js와 Tailwind CSS로 빠르게 웹 애플리케이션을 구축해보세요.
</p>
<button className="mt-6 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
시작하기
</button>
</div>
);
}
CSS-in-JS
Styled Components 또는 Emotion과 같은 라이브러리를 사용할 수 있습니다.
'use client';
import styled from 'styled-components';
const Card = styled.div`
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
background: white;
margin-bottom: 1rem;
`;
const Title = styled.h2`
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: #333;
`;
const Content = styled.p`
color: #666;
line-height: 1.5;
`;
export default function BlogPost() {
return (
<Card>
<Title>Next.js 시작하기</Title>
<Content>
Next.js는 React 기반의 풀스택 프레임워크입니다.
서버 사이드 렌더링, 정적 사이트 생성 등의 기능을 제공합니다.
</Content>
</Card>
);
}
최적화 기능
이미지 최적화
Next.js의 Image 컴포넌트는 자동으로 이미지를 최적화합니다.
import Image from 'next/image';
export default function ProfilePage() {
return (
<div>
<h1>프로필</h1>
<Image
src="/profile.jpg"
alt="프로필 이미지"
width={300}
height={300}
priority // LCP(Largest Contentful Paint) 이미지에 추가
/>
</div>
);
}
폰트 최적화
Next.js는 웹 폰트를 자동으로 최적화합니다.
import { Inter, Roboto } from 'next/font/google';
// 가변 폰트 사용
const inter = Inter({
subsets: ['latin'],
display: 'swap',
});
// 고정 폰트 사용
const roboto = Roboto({
weight: ['400', '700'],
style: ['normal', 'italic'],
subsets: ['latin'],
display: 'swap',
});
export default function Layout({ children }) {
return (
<html lang="ko">
<body className={inter.className}>
<header className={roboto.className}>
<h1>내 웹사이트</h1>
</header>
<main>{children}</main>
</body>
</html>
);
}
스크립트 최적화
외부 스크립트를 최적화하여 로딩할 수 있습니다.
import Script from 'next/script';
export default function Layout({ children }) {
return (
<>
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="afterInteractive" // 페이지 인터랙티브 후 로드
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
`}
</Script>
{children}
</>
);
}
Next.js와 React의 차이점 요약
렌더링 방식
- React: 기본적으로 클라이언트 사이드 렌더링(CSR)
- Next.js: 서버 사이드 렌더링(SSR), 정적 사이트 생성(SSG), 클라이언트 사이드 렌더링(CSR) 모두 지원
라우팅
- React: React Router와 같은 추가 라이브러리 필요
- Next.js: 파일 시스템 기반 라우팅 내장
데이터 페칭
- React: 직접 구현 (useEffect + fetch 등)
- Next.js: 서버 컴포넌트에서 async/await 지원, getStaticProps, getServerSideProps와 같은 데이터 페칭 함수 제공
최적화
- React: 직접 구현 필요
- Next.js: 이미지, 폰트, 스크립트 최적화 내장
백엔드 로직
- React: 별도의 백엔드 서버 필요
- Next.js: API 라우트로 백엔드 로직 구현 가능
빌드 및 배포
- React: 별도의 빌드 설정 필요 (webpack 등)
- Next.js: 빌드 최적화 내장, Vercel과의 원활한 통합
Next.js 실전 팁
환경 변수 사용하기
# .env.local
DATABASE_URL=mysql://user:password@localhost:3306/mydb
API_KEY=your_api_key
# .env.development
NEXT_PUBLIC_API_URL=http://localhost:3000/api
# .env.production
NEXT_PUBLIC_API_URL=https://example.com/api
// 서버 컴포넌트에서 사용
console.log(process.env.DATABASE_URL); // 안전하게 사용 가능, 클라이언트에 노출되지 않음
// 클라이언트 컴포넌트에서 사용
console.log(process.env.NEXT_PUBLIC_API_URL); // NEXT_PUBLIC_ 접두사가 있어야 함
국제화(i18n) 구현하기
// app/[lang]/layout.tsx
import { getDictionary } from '@/lib/dictionaries';
export async function generateStaticParams() {
return [
{ lang: 'en' },
{ lang: 'ko' },
{ lang: 'ja' }
];
}
export default async function Layout({
children,
params: { lang }
}) {
const dictionary = await getDictionary(lang);
return (
<html lang={lang}>
<body>
<header>
<h1>{dictionary.welcome}</h1>
</header>
{children}
</body>
</html>
);
}
성능 모니터링
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitals() {
useReportWebVitals(metric => {
console.log(metric);
// 분석 서비스로 전송
sendToAnalytics(metric);
});
return null;
}
// app/layout.tsx에 추가
export default function RootLayout({ children }) {
return (
<html>
<body>
<WebVitals />
{children}
</body>
</html>
);
}
요약
- Next.js는 React를 기반으로 한 풀스택 프레임워크입니다
- 서버 사이드 렌더링, 정적 사이트 생성, API 라우트 등 다양한 기능을 제공합니다
- 파일 시스템 기반 라우팅으로 쉽게 페이지와 API를 구성할 수 있습니다
- 이미지, 폰트, 스크립트 최적화 기능이 내장되어 있습니다
- React만으로는 직접 구현해야 할 많은 기능들이 Next.js에는 내장되어 있습니다
'Frameworks > React' 카테고리의 다른 글
React Query/TanStack 심화 (0) | 2025.04.02 |
---|---|
React 성능 분석 실전 (0) | 2025.04.02 |
테스트 전략 (Jest, RTL, Cypress) (0) | 2025.04.02 |
React + TypeScript 도입과 패턴 (0) | 2025.04.02 |
React 애플리케이션 성능 최적화 (0) | 2025.04.01 |