04. PHP → AJAX → React → Server Components: 나선형 진화
04. PHP → AJAX → React → Server Components: 나선형 진화
이 문서에서 배우는 것
- PHP가 이미 "상태-UI 동기화" 문제를 해결하고 있었다는 사실
- 웹이 "문서"에서 "앱"으로 바뀌면서 왜 AJAX가 필요해졌는지
- AJAX가 해결한 문제와 다시 만들어낸 문제
- React가 "브라우저 안의 PHP"인 이유
- Server Components가 PHP 회귀인지, 나선형 진화인지
PM의 요구사항 (시대별 변천)
2003년: "상품 목록 페이지 만들어주세요." → PHP로 서버에서 HTML 생성 2008년: "페이지 새로고침 없이 장바구니에 담기게 해주세요." → AJAX 도입 2015년: "Gmail 같은 실시간 앱을 만들어주세요." → React SPA 2023년: "첫 로딩은 빠르게, 인터랙션도 부드럽게 해주세요." → Server Components
같은 "웹 페이지"인데 요구사항이 계속 달라졌습니다. 그리고 놀랍게도, 해결책은 나선형으로 돌아오고 있습니다.
PHP가 이미 해결한 문제
PHP의 동작 방식
사용자 요청 서버 브라우저
│ │ │
│ GET /cart │ │
│─────────────────────▶│ │
│ │ 1. DB에서 장바구니 조회 │
│ │ 2. 데이터로 HTML 생성 │
│ │ 3. 완성된 HTML 응답 │
│ │──────────────────────────▶ │
│ │ │ HTML 표시
│ │ │
│ GET /cart (삭제 후) │ │
│─────────────────────▶│ │
│ │ 1. DB에서 장바구니 조회 │
│ │ 2. 데이터로 HTML 생성 │
│ │ 3. 완성된 HTML 응답 │
│ │──────────────────────────▶ │
│ │ │ HTML 전체 교체PHP 코드 예시
<?php
// cart.php — 요청이 올 때마다 처음부터 HTML을 만듦
$cart = getCartFromDB($userId);
$total = array_sum(array_map(fn($item) => $item['price'] * $item['qty'], $cart));
$totalQty = array_sum(array_column($cart, 'qty'));
?>
<h2>장바구니 (<?= count($cart) ?>개 상품)</h2>
<?php if (empty($cart)): ?>
<p>장바구니가 비어있습니다</p>
<?php else: ?>
<ul>
<?php foreach ($cart as $item): ?>
<li>
<?= $item['name'] ?> — <?= number_format($item['price'] * $item['qty']) ?>원
<a href="/cart/remove/<?= $item['id'] ?>">삭제</a>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<p>총 <?= count($cart) ?>종 / <?= $totalQty ?>권</p>
<p>합계: <?= number_format($total) ?>원</p>PHP에는 동기화 문제가 없었다
PHP의 핵심 원리:
매 요청마다 → 데이터를 DB에서 가져오고 → HTML을 처음부터 새로 만듦
→ "이전 화면"과 "현재 데이터"를 동기화할 필요가 없음
→ 왜? 이전 화면을 통째로 버리고 새로 만드니까
→ 동기화 문제가 구조적으로 발생할 수 없음01-core-problem.md에서 본 "상태-UI 동기화" 문제가 PHP에는 처음부터 없었습니다. 매번 새로 그리니까요.
하지만 웹이 "문서"에서 "앱"으로 바뀌었다
2005년 이전: 웹 = 문서
웹 페이지 = 종이 문서의 디지털 버전
클릭 → 서버 요청 → 새 페이지 전체 로드 → 흰 화면(깜빡임) → 새 페이지 표시
이게 정상이었음. "문서"를 넘기는 것이니까.2005년~: 웹 = 앱
이 시기에 혁명적인 서비스들이 등장합니다:
2004 Gmail — "이메일이 데스크톱 앱처럼 부드럽다"
2005 Google Maps — "지도를 드래그하면 실시간으로 움직인다"
2006 Facebook — "새 댓글이 새로고침 없이 나타난다"
2008 GitHub — "코드 파일을 클릭하면 바로 보인다"
2010 Twitter — "새 트윗이 실시간으로 올라온다"사용자의 기대가 바뀌었습니다:
이전: "링크 클릭하면 페이지 바뀌는 거지" (OK)
이후: "왜 클릭할 때마다 화면이 깜빡이지?" (불만)
"왜 버튼 누르면 1-2초 기다려야 하지?" (불만)
"왜 데스크톱 앱처럼 부드럽지 않지?" (불만)PHP의 한계가 드러남
PHP로 장바구니에서 상품 삭제:
1. "삭제" 클릭
2. 서버에 요청 (GET /cart/remove/2)
3. 서버가 DB 업데이트
4. 서버가 전체 HTML 새로 생성
5. 브라우저가 전체 페이지를 받아서 교체
6. 흰 화면 깜빡임 → 새 페이지 표시
문제:
┌──────────────────────────────────────┐
│ • 화면 전체가 깜빡임 │
│ • 스크롤 위치가 초기화됨 │
│ • 입력 중이던 텍스트가 사라짐 │
│ • 애니메이션이 중단됨 │
│ • 작업 시간: 1-3초 (네트워크 왕복) │
└──────────────────────────────────────┘이것은 "문서"에서는 문제가 아니었지만, "앱"에서는 치명적이었습니다.
AJAX의 등장: 데이터만 주고받자
AJAX란?
AJAX = Asynchronous JavaScript and XML
기존:
클릭 → 서버에 전체 페이지 요청 → 전체 HTML 수신 → 전체 교체 (깜빡임)
AJAX:
클릭 → 서버에 데이터만 요청 → JSON/XML 수신 → 필요한 부분만 DOM 수정 (부드러움)AJAX로 장바구니 삭제
// AJAX 방식 — 페이지 새로고침 없이 상품 삭제
function removeItem(itemId) {
// 1. 서버에 데이터만 요청
fetch('/api/cart/remove', {
method: 'POST',
body: JSON.stringify({ itemId: itemId })
})
.then(response => response.json())
.then(data => {
// 2. 서버에서 받은 데이터로 DOM을 직접 업데이트
document.querySelector(`[data-id="${itemId}"]`).remove();
document.querySelector('.cart-count').textContent =
`(${data.itemCount}개 상품)`;
document.querySelector('.total').textContent =
`합계: ${data.total.toLocaleString()}원`;
document.querySelector('.summary').textContent =
`총 ${data.itemCount}종 / ${data.totalQty}권`;
if (data.itemCount === 0) {
document.querySelector('.cart-list').innerHTML =
'<p>장바구니가 비어있습니다</p>';
}
// ... 더 많은 DOM 업데이트 ...
});
}AJAX가 해결한 것과 다시 만들어낸 것
AJAX가 해결한 것:
✅ 화면 깜빡임 없음
✅ 빠른 응답 (필요한 데이터만 전송)
✅ 스크롤 위치 유지
✅ 사용자 경험이 "앱"처럼 부드러움
AJAX가 다시 만들어낸 것:
❌ DOM 수동 업데이트 필요 → 01장에서 본 "상태-UI 동기화" 문제 재발!
❌ 상품 삭제 후 6곳을 일일이 업데이트해야 함
❌ 하나라도 빠뜨리면 데이터와 화면 불일치PHP가 "매번 다시 그리기"로 해결했던 동기화 문제가, AJAX에서 다시 나타났습니다.
시간순 정리: 동기화 문제의 여정
시기 기술 동기화 문제 사용자 경험
───────────────────────────────────────────────────────
2000~ PHP ✅ 해결 ❌ 깜빡임
(매번 새로 그림) (구조적으로 없음) (전체 페이지 교체)
2005~ AJAX ❌ 재발 ✅ 부드러움
(부분 DOM 수정) (수동 DOM 관리) (깜빡임 없음)
2013~ React ✅ 재해결 ✅ 부드러움
(선언적 UI) (자동 동기화) (Virtual DOM)
2023~ Server Components ✅ 해결 ✅ 부드러움 + ✅ 빠른 초기 로딩
(서버+클라이언트) (자동 동기화) (서버 렌더링 + 클라이언트 인터랙션)┌─────────────────────────────────────────────────────────────┐
│ │
│ PHP ──→ AJAX ──→ React ──→ Server Components │
│ 해결 재발 재해결 더 나은 해결 │
│ │
│ 같은 문제가 반복되지만, 매번 더 나은 수준에서 해결됨 │
│ 이것이 "나선형 진화" │
│ │
└─────────────────────────────────────────────────────────────┘React = "브라우저 안에서 PHP처럼 매번 다시 그리되, 바뀐 부분만 업데이트"
PHP와 React의 유사점
PHP:
요청 올 때마다 → 전체 HTML을 처음부터 새로 만듦
→ 동기화 문제 없음 (항상 최신 데이터 기반)
→ 대신 전체 페이지 깜빡임
React:
상태 바뀔 때마다 → 전체 UI를 "선언"으로 새로 만듦 (Virtual DOM)
→ 동기화 문제 없음 (항상 최신 상태 기반)
→ 대신 바뀐 부분만 실제 DOM에 반영 (깜빡임 없음)코드로 비교
// PHP — 서버에서 매번 처음부터
<?php $total = calculateTotal($cart); ?>
<p>합계: <?= number_format($total) ?>원</p>
<p>상품: <?= count($cart) ?>개</p>
<!-- 항상 최신. 동기화 불필요. 하지만 전체 페이지 교체. -->// React — 브라우저에서 매번 "처음부터" (선언)
function Cart({ cart }) {
const total = cart.reduce((sum, item) => sum + item.price * item.qty, 0);
return (
<>
<p>합계: {total.toLocaleString()}원</p>
<p>상품: {cart.length}개</p>
{/* 항상 최신. 동기화 불필요. 바뀐 부분만 DOM에 반영. */}
</>
);
}공통점: "현재 데이터를 기반으로 UI를 처음부터 만든다"
→ 이전 상태와 동기화할 필요가 없다
차이점:
PHP → 실제로 전체 HTML을 새로 만들어 보냄 (느림, 깜빡임)
React → Virtual DOM으로 "새로 만든 척"하고, 바뀐 부분만 반영 (빠름, 부드러움)Jordan Walke가 PHP(XHP)에서 영감을 받은 이유가 바로 이것입니다. PHP의 "매번 새로 그리기" 방식에서 동기화 문제가 없다는 점을 브라우저에서 재현한 것입니다.
Server Components는 PHP 회귀인가?
2023년, React 팀이 Server Components를 발표했을 때 많은 개발자들이 이렇게 말했습니다:
"이거... 그냥 PHP 아닌가?"
"서버에서 HTML 만들어 보내는 거면 2000년대로 돌아간 거 아닌가?"
"우리가 SPA를 왜 만들었는데, 다시 서버 렌더링이라고?"Server Components가 하는 일
// app/page.jsx — Server Component (서버에서 실행)
async function CartPage() {
// DB에 직접 접근 가능 — PHP처럼!
const cart = await db.query('SELECT * FROM cart WHERE user_id = ?', [userId]);
const total = cart.reduce((sum, item) => sum + item.price * item.qty, 0);
return (
<div>
<h2>장바구니 ({cart.length}개 상품)</h2>
<CartList items={cart} /> {/* 서버 컴포넌트 */}
<p>합계: {total.toLocaleString()}원</p>
<CheckoutButton total={total} /> {/* 클라이언트 컴포넌트 */}
</div>
);
}// components/CheckoutButton.jsx — Client Component (브라우저에서 실행)
'use client'; // ← 이 한 줄이 경계선
import { useState } from 'react';
function CheckoutButton({ total }) {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
setLoading(true);
await processPayment(total);
setLoading(false);
};
return (
<button onClick={handleCheckout} disabled={loading}>
{loading ? '처리 중...' : `${total.toLocaleString()}원 결제하기`}
</button>
);
}비슷한 점 (PHP와)
Server Components와 PHP의 공통점:
✅ 서버에서 HTML을 생성
✅ DB에 직접 접근 가능
✅ API 엔드포인트 없이 데이터 조회
✅ 클라이언트에 JavaScript를 보내지 않음 (서버 컴포넌트 부분)
✅ 보안에 유리 (DB 비밀번호 등이 브라우저에 노출되지 않음)다른 점 (PHP와)
Server Components가 PHP와 다른 점:
✅ 페이지 전환 시 깜빡임 없음
PHP: 링크 클릭 → 흰 화면 → 새 페이지
SC: 링크 클릭 → 부드러운 전환 → 바뀐 부분만 업데이트
✅ 클라이언트 인터랙션 유지
PHP: 버튼 클릭 → 서버 왕복 → 전체 페이지 새로고침
SC: 버튼 클릭 → 브라우저에서 즉시 반응 (Client Component)
✅ 상태 보존
PHP: 페이지 이동하면 스크롤 위치, 입력값, 애니메이션 다 초기화
SC: 페이지 이동해도 클라이언트 상태 유지 가능
✅ 컴포넌트 단위 선택
PHP: 전체 페이지가 서버에서 생성
SC: 컴포넌트마다 서버/클라이언트 실행 위치를 선택'use client' — 한 줄의 경계선
Server Components의 핵심 아이디어:
"모든 컴포넌트가 브라우저에서 실행될 필요는 없다"
장바구니 목록 표시 → DB에서 읽어서 HTML 만들기 → 서버에서 하면 됨
결제 버튼 클릭 → 사용자 인터랙션 처리 → 브라우저에서 해야 함
이 경계를 나누는 것이 'use client' 한 줄// 서버 컴포넌트 (기본값) — 서버에서 실행, JS 번들에 포함되지 않음
function ProductList() {
const products = await db.query('SELECT * FROM products');
return (
<ul>
{products.map(p => (
<li key={p.id}>
{p.name} — {p.price.toLocaleString()}원
<AddToCartButton productId={p.id} />
</li>
))}
</ul>
);
}
// 클라이언트 컴포넌트 — 브라우저에서 실행, JS 번들에 포함됨
'use client';
function AddToCartButton({ productId }) {
const [added, setAdded] = useState(false);
return (
<button onClick={() => {
addToCart(productId);
setAdded(true);
}}>
{added ? '담김!' : '장바구니 담기'}
</button>
);
}PHP vs CSR(SPA) vs Server Components 비교
| 관점 | PHP | CSR (React SPA) | Server Components |
|---|---|---|---|
| HTML 생성 위치 | 서버 | 브라우저 | 서버 + 브라우저 |
| DB 접근 | 서버에서 직접 | API를 통해 간접 | 서버 컴포넌트에서 직접 |
| 초기 로딩 | 빠름 (완성된 HTML) | 느림 (JS 다운로드 후 렌더) | 빠름 (서버 HTML + 필요한 JS만) |
| 페이지 전환 | 깜빡임 (전체 새로고침) | 부드러움 (클라이언트 라우팅) | 부드러움 (서버+클라이언트 협력) |
| 인터랙션 | 느림 (매번 서버 왕복) | 빠름 (브라우저에서 처리) | 빠름 (Client Component에서 처리) |
| JS 번들 크기 | 거의 없음 | 큼 (모든 로직이 클라이언트) | 작음 (서버 컴포넌트는 번들 미포함) |
| SEO | 좋음 | 나쁨 (JS 실행 후 콘텐츠 생성) | 좋음 (서버에서 HTML 생성) |
| 상태-UI 동기화 | 해결 (매번 새로 그림) | 해결 (선언적 UI) | 해결 (선언적 + 서버 렌더링) |
| 시대 | 2000~ | 2013~ | 2023~ |
시각적 비교
PHP (2000~):
[서버] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━▶ [완성된 HTML]
DB 조회 → HTML 생성 → 전체 전송 전체 교체 (깜빡임)
CSR/SPA (2013~):
[서버] ━━━▶ [빈 HTML + JS 번들]
↓
[브라우저] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━▶ [완성된 화면]
JS 다운로드 → API 호출 → 데이터 수신 → 렌더링
Server Components (2023~):
[서버] ━━━━━━━━━━━▶ [서버 HTML + 최소한의 JS]
DB 조회 → HTML 생성 ↓
[브라우저] ━━━━━━━━━━▶ [인터랙티브 화면]
서버 HTML 표시 + 클라이언트 JS로 인터랙션 추가"나선형 발전: 같은 문제의 더 나은 해결책"
나선형 진화란?
일직선 진화:
A → B → C → D
과거는 버리고 새것만 채택
나선형 진화:
D (Server Components)
╱
C (React SPA)
╱
B (AJAX)
╱
A (PHP)
위에서 내려다보면 A와 D가 비슷해 보이지만,
옆에서 보면 D가 훨씬 더 높은 곳에 있음나선형으로 보는 웹 개발:
PHP AJAX React Server
(서버) (클라이언트) (클라이언트) Components
(서버+클라이언트)
┌─────────────────────────────────────────────────────────┐
│ │
│ 렌더링 위치 서버 ─────▶ 클라이언트 ──▶ 클라이언트 ──▶ 둘 다 │
│ │
│ 동기화 문제 없음 ─────▶ 있음 ────────▶ 없음 ────────▶ 없음 │
│ │
│ 사용자 경험 나쁨 ─────▶ 좋음 ────────▶ 좋음 ────────▶ 더 좋음│
│ │
│ 초기 로딩 빠름 ─────▶ 보통 ────────▶ 느림 ────────▶ 빠름 │
│ │
└─────────────────────────────────────────────────────────┘각 단계에서 배운 것
PHP에서 배운 것:
"서버에서 매번 새로 그리면 동기화 문제가 없다"
→ React의 "매번 다시 선언" 아이디어의 원천
AJAX에서 배운 것:
"데이터만 주고받으면 훨씬 빠르고 부드럽다"
→ 페이지 전체를 교체할 필요 없다는 깨달음
React에서 배운 것:
"선언적 UI로 동기화 문제를 해결할 수 있다"
→ 하지만 모든 로직을 클라이언트에 보낼 필요는 없다
Server Components에서 배운 것:
"서버와 클라이언트를 컴포넌트 단위로 나눌 수 있다"
→ PHP의 장점(서버 렌더링) + React의 장점(선언적 UI + 인터랙션)기술은 부정(否定)이 아니라 통합(統合)
흔한 오해: "Server Components는 SPA를 부정한다"
"SPA는 서버 렌더링을 부정했다"
"새 기술은 이전 기술을 대체한다"
실제: 각 세대가 이전 세대의 장점을 흡수하면서 진화
PHP의 장점 (서버 렌더링, DB 직접 접근, 빠른 초기 로딩)
↓ 흡수
AJAX의 장점 (깜빡임 없는 업데이트, 부드러운 인터랙션)
↓ 흡수
React의 장점 (선언적 UI, 컴포넌트 시스템, 자동 동기화)
↓ 흡수
Server Components = 이 모든 장점의 통합역사를 알면 보이는 것들
Server Components를 처음 접했을 때:
"이게 뭐야? 서버에서 HTML을 만든다고?"
PHP → AJAX → React 역사를 알고 난 후:
"아, PHP의 서버 렌더링 장점을 React의 선언적 시스템 안에서 다시 살린 거구나"
"동기화 문제 해결(React)은 유지하면서, 초기 로딩 속도(PHP)를 가져온 거구나"
Astro, Qwik, Fresh 같은 새 프레임워크를 접했을 때:
"이것들도 같은 나선형 위에 있구나"
"서버와 클라이언트의 최적 경계를 찾는 과정이구나"시니어 개발자의 한마디
후배: "Server Components가 PHP 회귀라는 말이 많던데요?"
시니어: "위에서 내려다보면 비슷해 보여. 서버에서 HTML을 만들어 보내니까. 하지만 옆에서 보면 완전히 다른 높이에 있어. PHP에서는 페이지 전환마다 화면이 깜빡였고, 인터랙션마다 서버를 왕복해야 했잖아. Server Components는 서버 렌더링의 장점은 취하면서 그 단점들은 해결했어."
후배: "그러면 앞으로도 이런 나선형이 계속되나요?"
시니어: "아마 그럴 거야. 10년 후에는 또 누군가가 '이거 결국 React와 비슷하지 않나?'라고 할 거야. 하지만 실제로는 React가 해결 못한 문제를 해결하는 더 높은 수준의 기술이겠지. 기술의 역사를 알면, 새로운 기술이 나왔을 때 '왜 이게 나왔는지'를 이해할 수 있어. 그게 단순히 API를 외우는 것보다 훨씬 중요해."
핵심 정리
- PHP의 강점: 매 요청마다 HTML을 처음부터 새로 만듦 → 상태-UI 동기화 문제가 구조적으로 없음
- 웹의 변화: "문서"에서 "앱"으로 바뀌면서 깜빡임 없는 빠른 인터랙션이 필요해짐 → AJAX 등장
- AJAX의 역설: 깜빡임을 해결했지만, DOM 수동 관리로 동기화 문제가 재발
- React의 해결: "브라우저 안에서 PHP처럼 매번 다시 그리되, 바뀐 부분만 업데이트" → 동기화 문제 재해결
- Server Components의 통합: PHP의 서버 렌더링 장점 + React의 선언적 UI + 깜빡임 없는 인터랙션
- 나선형 진화: 같은 문제(상태-UI 동기화, 사용자 경험)가 매번 더 높은 수준에서 해결됨
- 핵심 교훈: 새 기술은 이전 기술을 부정하는 것이 아니라, 이전 세대의 장점을 흡수하며 진화
다음 문서에서는 이 프레임워크들을 만든 사람들의 이야기를 살펴봅니다. React, Svelte, Vue, Express, Linux — 이 기술들을 만든 사람들의 공통점은 무엇일까요?