Next.js 확장과 차이점

2025. 4. 2. 16:43Frameworks/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