React & Next.js 입문 가이드 | 2026년 기준

React & Next.js 입문 가이드 | 2026년 기준

이 글의 핵심

클라이언트 컴포넌트와 Hooks로 UI를 만들고, Next.js 쪽에서는 라우팅·서버 컴포넌트 개념까지 이어지게 정리했다. 버전 숫자보다 “어디 코드가 도는지”를 먼저 맞추는 쪽에 맞췄다.


목차

  1. React란 무엇인가?
  2. React 시작하기
  3. 컴포넌트와 JSX
  4. Hooks 정리
  5. 상태 관리
  6. Next.js 소개
  7. App Router와 Server Components
  8. 실전 프로젝트

사전 지식 (초보자를 위한 기초)

1. HTML, CSS, JavaScript 기초

HTML·CSS·JavaScript 기초가 있으면 이 글을 따라가기 쉽다.

HTML (구조)

아래 코드는 html를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

<!DOCTYPE html>
<html>
<head>
  <title>My Page</title>
</head>
<body>
  <h1>Hello World</h1>
  <button id="btn">Click me</button>
</body>
</html>

CSS (스타일)

아래 코드는 css를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

h1 {
  color: blue;
  font-size: 24px;
}

button {
  background-color: #007bff;
  color: white;
  padding: 10px 20px;
}

JavaScript (동작)

다음은 간단한 javascript 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

const button = document.getElementById('btn');
button.addEventListener('click', () => {
  alert('Button clicked!');
});

2. ES6+ 문법

React 코드에서 자주 마주치는 ES6+ 문법이다.

화살표 함수

아래 코드는 javascript를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 기존 함수
function add(a, b) {
  return a + b;
}

// 화살표 함수
const add = (a, b) => a + b;

구조 분해 할당

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 객체
const user = { name: 'Alice', age: 25 };
const { name, age } = user;

// 배열
const numbers = [1, 2, 3];
const [first, second] = numbers;

스프레드 연산자

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 변수 선언 및 초기화
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];  // [1, 2, 3, 4, 5]

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };  // { a: 1, b: 2, c: 3 }

템플릿 리터럴

const name = 'Alice';
const message = `Hello, ${name}!`;  // "Hello, Alice!"

3. npm과 패키지 관리

npm은 Node 생태계에서 패키지를 설치·관리하는 기본 도구다.

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# Node.js 설치 확인
node --version
npm --version

# 프로젝트 초기화
npm init -y

# 패키지 설치
npm install react react-dom

# 개발 의존성 설치
npm install --save-dev webpack

1. React란 무엇인가?

React의 핵심 개념

React는 Meta가 만든 UI 라이브러리다.

핵심 특징:

아래 코드는 text를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

1. 컴포넌트 기반
   - UI를 재사용 가능한 조각으로 분리
   - 레고 블록처럼 조립

2. 선언적 (Declarative)
   - "무엇을" 보여줄지 선언
   - "어떻게" 구현할지는 React가 처리

3. Virtual DOM
   - 변경사항을 효율적으로 업데이트
   - 빠른 렌더링

React vs Vanilla JavaScript

Vanilla JavaScript (명령형)

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 카운터 구현
let count = 0;
const button = document.getElementById('btn');
const display = document.getElementById('count');

button.addEventListener('click', () => {
  count++;
  display.textContent = count;  // DOM 직접 조작
});

React (선언형)

아래 코드는 jsx를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

// 상태가 변하면 자동으로 UI 업데이트!

2. React 시작하기

Vite로 프로젝트 생성

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Vite로 React 프로젝트 생성 (빠름!)
npm create vite@latest my-react-app -- --template react

cd my-react-app
npm install
npm run dev

# 브라우저에서 http://localhost:5173 접속

프로젝트 구조

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

my-react-app/
├── public/          # 정적 파일
├── src/
│   ├── App.jsx      # 메인 컴포넌트
│   ├── main.jsx     # 진입점
│   └── index.css    # 스타일
├── index.html
├── package.json
└── vite.config.js

첫 번째 컴포넌트

src/App.jsx

아래 코드는 jsx를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function App() {
  return (
    <div>
      <h1>Hello React!</h1>
      <p>My first React app</p>
    </div>
  );
}

export default App;

3. 컴포넌트와 JSX

컴포넌트란?

컴포넌트는 UI를 나누는 재사용 단위다.

다음은 jsx를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Button 컴포넌트
function Button({ text, onClick }) {
  return (
    <button onClick={onClick}>
      {text}
    </button>
  );
}

// 여러 곳에서 재사용
function App() {
  return (
    <div>
      <Button text="저장" onClick={() => console.log('저장')} />
      <Button text="취소" onClick={() => console.log('취소')} />
      <Button text="삭제" onClick={() => console.log('삭제')} />
    </div>
  );
}

JSX 문법

JSX는 JavaScript 안에서 마크업을 쓰기 위한 문법이다.

아래 코드는 jsx를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// JSX
const element = <h1>Hello, {name}!</h1>;

// 컴파일 후 JavaScript
const element = React.createElement('h1', null, `Hello, ${name}!`);

JSX 규칙:

다음은 jsx를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 1. 하나의 부모 요소로 감싸기
// ❌ 에러
function App() {
  return (
    <h1>Title</h1>
    <p>Content</p>
  );
}

// ✅ Fragment 사용
function App() {
  return (
    <>
      <h1>Title</h1>
      <p>Content</p>
    </>
  );
}

// 2. JavaScript 표현식은 {} 안에
function App() {
  const name = 'Alice';
  const age = 25;
  
  return <p>Name: {name}, Age: {age + 1}</p>;
}

// 3. className 사용 (class는 예약어)
<div className="container">Content</div>

// 4. 자체 닫기 태그
<img src="image.jpg" />
<input type="text" />

4. Hooks 정리

useState (상태 관리)

useState는 컴포넌트에 상태를 붙인다.

다음은 jsx를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import { useState } from 'react';

function Counter() {
  // [현재값, 업데이트 함수] = useState(초기값)
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

여러 상태 관리:

다음은 jsx를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);
  
  return (
    <form>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
        placeholder="Name"
      />
      <input 
        value={email} 
        onChange={(e) => setEmail(e.target.value)} 
        placeholder="Email"
      />
      <input 
        type="number"
        value={age} 
        onChange={(e) => setAge(Number(e.target.value))} 
        placeholder="Age"
      />
    </form>
  );
}

useEffect (부수 효과)

useEffect는 렌더 이후에 돌릴 부수 효과(fetch, 구독 등)를 둔다.

다음은 jsx를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // 컴포넌트 마운트 시 실행
    async function fetchUser() {
      setLoading(true);
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      setUser(data);
      setLoading(false);
    }
    
    fetchUser();
  }, [userId]);  // userId 변경 시 재실행
  
  if (loading) return <p>Loading...</p>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

의존성 배열:

다음은 jsx를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 1. 빈 배열: 마운트 시 1번만 실행
// 실행 예제
useEffect(() => {
  console.log('Component mounted');
}, []);

// 2. 의존성 있음: 의존성 변경 시 실행
useEffect(() => {
  console.log('Count changed:', count);
}, [count]);

// 3. 의존성 없음: 매 렌더링마다 실행 (비추천)
useEffect(() => {
  console.log('Every render');
});

정리 함수 (Cleanup)

아래 코드는 jsx를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

useEffect(() => {
  // 타이머 시작
  const timer = setInterval(() => {
    console.log('Tick');
  }, 1000);
  
  // 정리 함수 (컴포넌트 언마운트 시 실행)
  return () => {
    clearInterval(timer);
  };
}, []);

useContext (전역 상태)

Context는 props를 깊게 파지 않고 전역에 가깝게 값을 공유할 때 쓴다.

다음은 jsx를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import { createContext, useContext, useState } from 'react';

// Context 생성
const ThemeContext = createContext();

// Provider 컴포넌트
function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Header />
      <Content />
    </ThemeContext.Provider>
  );
}

// Context 사용
function Header() {
  const { theme, setTheme } = useContext(ThemeContext);
  
  return (
    <header style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
      <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
        Toggle Theme
      </button>
    </header>
  );
}

function Content() {
  const { theme } = useContext(ThemeContext);
  
  return (
    <div style={{ color: theme === 'dark' ? '#fff' : '#000' }}>
      <p>Current theme: {theme}</p>
    </div>
  );
}

useMemo와 useCallback (성능 최적화)

useMemo: 계산 결과 캐싱

아래 코드는 jsx를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import { useMemo } from 'react';

function ExpensiveComponent({ numbers }) {
  // numbers가 변하지 않으면 재계산 안함
  const sum = useMemo(() => {
    console.log('Calculating sum...');
    return numbers.reduce((a, b) => a + b, 0);
  }, [numbers]);
  
  return <p>Sum: {sum}</p>;
}

useCallback: 함수 캐싱

아래 코드는 jsx를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import { useCallback } from 'react';

function Parent() {
  const [count, setCount] = useState(0);
  
  // count가 변하지 않으면 같은 함수 재사용
  const handleClick = useCallback(() => {
    console.log('Clicked:', count);
  }, [count]);
  
  return <Child onClick={handleClick} />;
}

5. 상태 관리

Props vs State

Props (속성)

  • 부모 → 자식으로 전달
  • 읽기 전용 (변경 불가)

아래 코드는 jsx를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function Parent() {
  return <Child name="Alice" age={25} />;
}

function Child({ name, age }) {
  // props는 변경 불가
  // name = "Bob";  // ❌ 에러
  
  return <p>{name} is {age} years old</p>;
}

State (상태)

  • 컴포넌트 내부 데이터
  • 변경 가능 (setState)

아래 코드는 jsx를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

function Counter() {
  const [count, setCount] = useState(0);
  
  // state는 변경 가능
  const increment = () => setCount(count + 1);
  
  return <button onClick={increment}>{count}</button>;
}

상태 끌어올리기 (Lifting State Up)

다음은 jsx를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 각 컴포넌트가 독립적인 상태
function App() {
  return (
    <>
      <Counter />  {/* count: 0 */}
      <Counter />  {/* count: 0 */}
    </>
  );
}

// ✅ 부모에서 상태 관리
function App() {
  const [count, setCount] = useState(0);
  
  return (
    <>
      <Display count={count} />
      <Button setCount={setCount} />
    </>
  );
}

function Display({ count }) {
  return <p>Count: {count}</p>;
}

function Button({ setCount }) {
  return <button onClick={() => setCount(c => c + 1)}>+1</button>;
}

복잡한 상태 관리: useReducer

다음은 jsx를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import { useReducer } from 'react';

// Reducer 함수
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+1</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

6. Next.js 소개

Next.js란?

Next.js는 React 위에 라우팅·빌드·SSR 등을 얹은 풀스택 프레임워크다.

React vs Next.js:

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

React (라이브러리):
- UI만 담당
- 라우팅, SSR 등은 직접 구현

Next.js (프레임워크):
- React + 라우팅 + SSR + 빌드 최적화
- 즉시 프로덕션 준비 완료

Next.js 장점:

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

✅ 파일 기반 라우팅
✅ SSR (Server-Side Rendering)
✅ SSG (Static Site Generation)
✅ API Routes (백엔드 기능)
✅ 이미지 최적화
✅ 자동 코드 분할

Next.js 시작하기

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# Next.js 프로젝트 생성
npx create-next-app@latest my-next-app

# 옵션 선택:
# ✓ TypeScript? Yes
# ✓ ESLint? Yes
# ✓ Tailwind CSS? Yes
# ✓ App Router? Yes

cd my-next-app
npm run dev

# http://localhost:3000 접속

7. App Router와 Server Components

App Router (Next.js 13+)

파일 기반 라우팅:

아래 코드는 text를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

app/
├── page.js          → /
├── about/
│   └── page.js      → /about
├── blog/
│   ├── page.js      → /blog
│   └── [slug]/
│       └── page.js  → /blog/hello-world
└── api/
    └── users/
        └── route.js → /api/users

예제: 블로그 페이지

app/blog/page.js

아래 코드는 jsx를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

export default function BlogPage() {
  return (
    <div>
      <h1>Blog</h1>
      <ul>
        <li><a href="/blog/first-post">First Post</a></li>
        <li><a href="/blog/second-post">Second Post</a></li>
      </ul>
    </div>
  );
}

app/blog/[slug]/page.js

아래 코드는 jsx를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

export default function BlogPost({ params }) {
  const { slug } = params;
  
  return (
    <div>
      <h1>Post: {slug}</h1>
      <p>Content goes here...</p>
    </div>
  );
}

Server Components

Server Components서버에서만 실행·렌더링되는 컴포넌트다.

다음은 jsx를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app/posts/page.js (Server Component)
async function getPosts() {
  const res = await fetch('https://api.example.com/posts');
  return res.json();
}

export default async function PostsPage() {
  // 서버에서 데이터 fetch (클라이언트에 전송 안됨)
  const posts = await getPosts();
  
  return (
    <div>
      <h1>Posts</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  );
}

Client Components (상호작용 필요)

아래 코드는 jsx를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 실행 예제
'use client';  // 클라이언트 컴포넌트 선언

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

언제 무엇을 사용할까?

아래 코드는 text를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

Server Component 사용:
✅ 데이터 fetch
✅ 백엔드 리소스 접근
✅ 민감한 정보 (API 키)
✅ 큰 의존성 (서버에만 로드)

Client Component 사용:
✅ 상호작용 (onClick, onChange)
✅ useState, useEffect 등 Hooks
✅ 브라우저 API (localStorage, window)

8. 데이터 Fetching

Server Component에서 데이터 가져오기

다음은 jsx를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app/users/page.js
async function getUsers() {
  const res = await fetch('https://api.example.com/users', {
    cache: 'no-store'  // 항상 최신 데이터
  });
  return res.json();
}

export default async function UsersPage() {
  const users = await getUsers();
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

캐싱 전략

아래 코드는 jsx를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 1. 캐싱 안함 (항상 최신)
fetch(url, { cache: 'no-store' });

// 2. 캐싱 (기본값)
fetch(url, { cache: 'force-cache' });

// 3. 재검증 (10초마다)
fetch(url, { next: { revalidate: 10 } });

Loading과 Error 처리

app/posts/loading.js

export default function Loading() {
  return <p>Loading posts...</p>;
}

app/posts/error.js

아래 코드는 jsx를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

'use client';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

9. 실전 프로젝트: Todo 앱

프로젝트 구조

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

app/
├── page.js          # 홈 (Todo 목록)
├── layout.js        # 레이아웃
└── api/
    └── todos/
        └── route.js # API 엔드포인트

구현

app/page.js

다음은 jsx를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 실행 예제
'use client';

import { useState, useEffect } from 'react';

export default function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');
  
  // 초기 데이터 로드
  useEffect(() => {
    fetch('/api/todos')
      .then(res => res.json())
      .then(data => setTodos(data));
  }, []);
  
  // Todo 추가
  const addTodo = async () => {
    if (!input.trim()) return;
    
    const response = await fetch('/api/todos', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ text: input })
    });
    
    const newTodo = await response.json();
    setTodos([...todos, newTodo]);
    setInput('');
  };
  
  // Todo 완료 토글
  const toggleTodo = async (id) => {
    const todo = todos.find(t => t.id === id);
    
    await fetch(`/api/todos/${id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ completed: !todo.completed })
    });
    
    setTodos(todos.map(t => 
      t.id === id ? { ...t, completed: !t.completed } : t
    ));
  };
  
  // Todo 삭제
  const deleteTodo = async (id) => {
    await fetch(`/api/todos/${id}`, { method: 'DELETE' });
    setTodos(todos.filter(t => t.id !== id));
  };
  
  return (
    <div style={{ maxWidth: '600px', margin: '50px auto' }}>
      <h1>Todo App</h1>
      
      {/* 입력 폼 */}
      <div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && addTodo()}
          placeholder="What needs to be done?"
          style={{ flex: 1, padding: '10px' }}
        />
        <button onClick={addTodo} style={{ padding: '10px 20px' }}>
          Add
        </button>
      </div>
      
      {/* Todo 목록 */}
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {todos.map(todo => (
          <li 
            key={todo.id}
            style={{
              display: 'flex',
              alignItems: 'center',
              gap: '10px',
              padding: '10px',
              borderBottom: '1px solid #eee'
            }}
          >
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{
              flex: 1,
              textDecoration: todo.completed ? 'line-through' : 'none',
              color: todo.completed ? '#999' : '#000'
            }}>
              {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

app/api/todos/route.js

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

let todos = [
  { id: 1, text: 'Learn React', completed: false },
  { id: 2, text: 'Build a project', completed: false }
];

let nextId = 3;

// GET /api/todos
export async function GET() {
  return Response.json(todos);
}

// POST /api/todos
export async function POST(request) {
  const { text } = await request.json();
  const newTodo = { id: nextId++, text, completed: false };
  todos.push(newTodo);
  return Response.json(newTodo);
}

// PATCH /api/todos/:id
export async function PATCH(request) {
  const { id, completed } = await request.json();
  const todo = todos.find(t => t.id === id);
  if (todo) {
    todo.completed = completed;
  }
  return Response.json(todo);
}

// DELETE /api/todos/:id
export async function DELETE(request) {
  const url = new URL(request.url);
  const id = Number(url.pathname.split('/').pop());
  todos = todos.filter(t => t.id !== id);
  return Response.json({ success: true });
}

10. 스타일링

CSS Modules

아래 코드는 jsx를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Button.module.css
.button {
  background-color: #007bff;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
}

.button:hover {
  background-color: #0056b3;
}

아래 코드는 jsx를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Button.jsx
import styles from './Button.module.css';

export default function Button({ children, onClick }) {
  return (
    <button className={styles.button} onClick={onClick}>
      {children}
    </button>
  );
}

Tailwind CSS

아래 코드는 jsx를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// tailwind.config.js 설정 후
export default function Button({ children, onClick }) {
  return (
    <button 
      className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
      onClick={onClick}
    >
      {children}
    </button>
  );
}

11. 폼 처리

Controlled Components

다음은 jsx를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Login:', { email, password });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button type="submit">Login</button>
    </form>
  );
}

React Hook Form (추천)

npm install react-hook-form

다음은 jsx를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import { useForm } from 'react-hook-form';

function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();
  
  const onSubmit = (data) => {
    console.log(data);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('email', { 
          required: '이메일을 입력하세요',
          pattern: {
            value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
            message: '유효한 이메일을 입력하세요'
          }
        })}
        placeholder="Email"
      />
      {errors.email && <p>{errors.email.message}</p>}
      
      <input
        type="password"
        {...register('password', { 
          required: '비밀번호를 입력하세요',
          minLength: {
            value: 8,
            message: '8자 이상 입력하세요'
          }
        })}
        placeholder="Password"
      />
      {errors.password && <p>{errors.password.message}</p>}
      
      <button type="submit">Login</button>
    </form>
  );
}

12. 상태 관리 라이브러리

Zustand (추천)

npm install zustand

아래 코드는 javascript를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

// store.js
import { create } from 'zustand';

export const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 })
}));

다음은 jsx를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Counter.jsx
import { useStore } from './store';

export default function Counter() {
  const { count, increment, decrement, reset } = useStore();
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

13. 성능 최적화

React.memo (불필요한 리렌더링 방지)

아래 코드는 jsx를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import { memo } from 'react';

// ❌ 부모가 리렌더링되면 자식도 리렌더링
function Child({ name }) {
  console.log('Child rendered');
  return <p>{name}</p>;
}

// ✅ props가 변하지 않으면 리렌더링 안함
const Child = memo(function Child({ name }) {
  console.log('Child rendered');
  return <p>{name}</p>;
});

코드 분할 (Lazy Loading)

아래 코드는 jsx를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import { lazy, Suspense } from 'react';

// 동적 import (필요할 때만 로드)
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <HeavyComponent />
    </Suspense>
  );
}

14. 배포

Vercel 배포 (추천)

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Vercel CLI 설치
npm install -g vercel

# 배포
vercel

# 프로덕션 배포
vercel --prod

환경 변수

# .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://...

아래 코드는 jsx를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 클라이언트에서 접근 (NEXT_PUBLIC_ 접두사 필요)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;

// 서버에서만 접근
const dbUrl = process.env.DATABASE_URL;

FAQ

Q1. React vs Vue vs Angular?

  • React: 가장 인기, 생태계 방대, 자유도 높음
  • Vue: 배우기 쉬움, 한국 커뮤니티 활발
  • Angular: 대규모 엔터프라이즈, 학습 곡선 높음

Q2. Next.js를 꼭 써야 하나?

아니다. 다만 SEO·SSR·파일 기반 라우팅까지 한 번에 가져가고 싶으면 Next.js가 선택지가 된다. Vite+React만으로도 충분한 프로젝트가 많다.

Q3. TypeScript를 써야 하나?

팀·코드베이스 규모가 커질수록 타입이 도움이 된다. 자동 완성·리팩터링 안전성이 체감된다. 작은 실험만 할 때는 JavaScript로 시작해도 된다.

Q4. 상태 관리 라이브러리가 필요한가요?

프로젝트 규모에 따라:

  • 소규모: useState + Context API
  • 중규모: Zustand
  • 대규모: Redux Toolkit

요약

핵심 정리

React:

  • 컴포넌트 기반 UI 라이브러리
  • Hooks (useState, useEffect)
  • Virtual DOM

Next.js:

  • React 풀스택 프레임워크
  • App Router, Server Components
  • SSR, SSG 지원

학습 로드맵:

  1. JavaScript ES6+ 문법
  2. React 기초 (컴포넌트, Props, State)
  3. React Hooks
  4. Next.js 기초
  5. 실전 프로젝트

다음 글 추천


키워드: React, Next.js, Frontend, JavaScript, TypeScript, Hooks, useState, useEffect, SSR, App Router, Server Components

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3