초기의 웹 사이트
초기의 웹 사이트는 유저 인터랙션이 적고 컴퓨터의 성능이 그다지 좋지 않았기 때문에 단순히 HTML과 CSS를 이용해서 페이지를 보여주기만 하는 정적인 애플리케이션이 많았다.
그렇기 때문에 페이지에 맞는 HTML 파일을 만들어놓고 클라이언트가 요청했을 때 그에 맞는 페이지를 서버에서 전송해주는 MPA 형식의 웹 사이트가 많았다.
MPA (Multi Page Application)
MPA는 이름 그대로 여러 페이지로 구성된 웹 어플리케이션이다.
서버에 미리 만들어져 있는 정적인 HTML 파일을 클라이언트가 받아 화면에 표시하고, 페이지가 이동할 때마다 HTML 파일을 새로 다운로드해 화면을 업데이트 하는 방식을 사용했다.

- 클라이언트가 서버에 리소스를 요청하고 서버에서는 해당 요청에 맞는 페이지의 HTML 파일을 응답해준다.
- 그 후에 애플리케이션에서 링크를 이동하거나 서버에 변경 사항을 요청 했을 때, 서버에서는 새로운 HTML 파일을 응답해준다.
이렇게 MPA 방식으로 웹 페이지가 동작할 때 사용자는 같은 웹 어플리케이션 내에서 동작을 취하지만, 페이지가 깜박거리며 새로운 HTML 파일을 불러오기 때문에 지연 시간이 생기고 불편함을 느낀다.
또한 작은 변화더라도 매번 페이지 전체를 불러와 변경시켜 서버의 비용이 증가하는 단점도 있다.
이러한 불편함들과 이전보다 웹 내에서의 유저 인터랙션이 많아진 점, 컴퓨터의 성능이 증가한 점, 모바일 사용자가 늘어나 연속되는 페이지 간의 유저 경험을 중요시하게 되면서 SPA 웹 어플리케이션이 등장했고 많이 사용되게 되었다.
SPA (Single Page Application)
SPA는 이름 그대로 하나의 페이지로 구성된 웹 어플리케이션이다.
서버에서 하나의 HTML만 받아서 동적으로 화면을 바꾸는 방식이다.
빈 HTML 파일을 브라우저가 받고 자바스크립트를 이용해 화면에 표시하고, 페이지가 이동할 때마다 자바스크립트를 이용해 동적으로 화면을 업데이트한다.

- 초기에 서버에 리소스를 요청하고 서버에서는 해당 요청에 맞는 페이지의 HTML 파일을 응답해준다.
- 그 후에 애플리케이션에서 링크를 이동하거나 서버에 변경 사항이 있어도 서버에는 AJAX를 통해 필요한 부분만 요청을 하고 서버에서도 변경할 부분의 데이터만 응답해 불필요한 서버 요청을 줄였다.
AJAX (Asynchronous JavaScript And XML)JavaScript와 XML (현재는 대부분 JSON)을 이용해 서버와 필요한 부분만 요청하고 응답 받는 기법이라고 할 수 있다.AJAX를 통해 변경이 필요한 부분만 서버에 요청하고 응답 받으면서 MPA의 단점이었던 페이지 깜박거림과 지연 시간을 줄여 서버 부하 감소와 사용자 경험을 높였다.
웹의 렌더링 방식
MPA와 SSR은 같지않고 SPA와 CSR은 같지않다.
그러나 대부분의 MPA는 SSR 방식으로 화면을 렌더링하고 SPA는 CSR 방식으로 화면을 렌더링한다.
CSR (Client Side Rendering)
CSR은 클라이언트 사이드 렌더링으로 클라이언트에서 하나의 HTML 파일만을 받고 나머지는 JS 파일을 이용해 화면을 렌더링 하는 방식을 말한다.

- 클라이언트가 서버에 요청을 하면 하나의 빈 HTML파일을 브라우저에 응답해준다.
- HTML파일에는 내용이 없기 때문에 브라우저는 즉시 JS 파일을 다운로드한다.
- JS 파일을 다운로드 후에 자바스크립트를 통해 동적으로 화면을 렌더링한다. 화면이 브라우저에 보이는 순간부터 사용자는 상호작용이 가능하다.
- 이 후에 페이지 이동할 때는 AJAX를 이용해 서버에 필요한 부분만 요청해 화면을 렌더링한다.
CSR 방식은 CRA를 했을 때 만들어지는 아래 코드처럼 HTML 마크업이 아닌 자바스크립트 파일을 이용해 동적으로 화면을 렌더링하기 때문에 화면을 보기까지의 시간은 느리지만 화면이 렌더링 된 후 사용자가 인터랙션 하기까지의 시간은 빠르다.
"use client"; import { useEffect, useState } from "react"; async function fetchPosts() { const res = await fetch("https://jsonplaceholder.typicode.com/posts"); const data = await res.json(); return data; } export default function CSRPage() { const [post, setPost] = useState([]); useEffect(() => { fetchPosts().then((data) => { setPost(data); console.log("csr - render"); }); }, []); return ( <main> <h1>CSR PAGE</h1> {JSON.stringify(post)} </main> ); }
SSR (Server Side Rendering)
SSR은 서버 사이드 렌더링으로 클라이언트가 요청했을 때 서버에서 HTML 파일을 컴파일해 브라우저에 전달하고 브라우저는 즉시 렌더링 하는 방식을 말한다.

- 클라이언트가 서버에 요청을 하면 클라이언트의 요청에 맞는 HTML파일을 컴파일 후에 브라우저에 응답해준다.
- 브라우저는 HTML 파일을 화면에 렌더링한다.이 때부터 JS파일을 다운로드한다.
- JS파일이 모두 다운로드되면 사용자와의 상호작용이 가능해진다.
- 이 후에 페이지 이동할 때는 똑같이 서버에서 미리 렌더링 한 HTML파일을 응답해주는 과정이 반복된다.
SSR 방식은 서버에서 미리 화면을 렌더링 해 브라우저에 전달하기 때문에 유저가 화면을 보기까지의 시간은 빠르지만 JS파일이 모두 다운로드 되어야 사용자의 인터랙션이 가능해 인터랙션 하기까지의 시간은 느리다.
async function fetchPosts() { const res = await fetch("https://jsonplaceholder.typicode.com/posts", { cache: "no-store", }); const data = await res.json(); return data; } export default async function SSRPage() { const posts = await fetchPosts(); console.log("ssr - render"); return ( <main> <h1>SSR PAGE</h1> {JSON.stringify(posts)}; </main> ); }
SSG
서버에서 페이지를 렌더링하여 클라이언트에게 HTML을 전달하는 방식
이름에서 처럼 사전에 미리 정적 페이지를 만들어놓는다. 이후 클라이언트에서 홈페이지를 요청하면 서버에서 만들어져있는 사이트를 바로 제공하여, 브라우저에서는 UI를 띄워주면 된다.

async function fetchPosts() { const res = await fetch("https://jsonplaceholder.typicode.com/posts", { cache: "force-cache", }); const data = await res.json(); return data; } export default async function SSGPage() { const posts = await fetchPosts(); console.log("ssg - render"); return ( <main> <h1>SSG PAGE</h1> {JSON.stringify(posts)}; </main> ); }
ISR
SSG 처럼 서버에서 페이지를 렌더링하여 클라이언트에게 HTML을 전달하는 방식이다.
차이점은 설정한 주기마다 페이지를 재생성하여 새로운 페이지를 전달한다.
즉, SSG는 빌드 타임에 페이지가 생성된 후 더 이상 수정되지 않지만,
ISR은 특정 주기마다 페이지를 다시 생성한다.

async function fetchPosts() { const res = await fetch("https://jsonplaceholder.typicode.com/posts", { next: { revalidate: 100 }, }); const data = await res.json(); return data; } export default async function ISRPage() { const posts = await fetchPosts(); console.log("isr - render"); return ( <main> <h1>ISR PAGE</h1> {JSON.stringify(posts)}; </main> ); }
실험하기
아래와 같은 지표들을 측정하려고 한다.
TTFB (Time to Fisrt Byte)
- 정의: 사용자가 페이지 요청을 보낸 순간부터, 브라우저가 서버로부터 첫 번째 바이트의 응답을 받기까지 걸린 시간.
- 의미: 네트워크와 서버 성능을 모두 반영하는 지표.
→ 서버 렌더링(SSR)에서는 특히 중요하고, CDN 캐싱(SSG/ISR)에서는 크게 단축됨.
for r in csr ssr ssg isr; do awk -F',' -v r="$r" '$2~"route="r && $3~"mode=warm"{ for(i=1;i<=NF;i++) if($i~/^ttfb=/){split($i,a,"="); print a[2]} }' perf.csv | sort -n | awk -v r="$r" '{ a[NR]=$1 } END { if(NR){ m = (NR%2)?a[(NR+1)/2]:(a[NR/2]+a[NR/2+1])/2; printf "%s TTFB_p50=%fs\n", r, m } }' done
결과

csr TTFB_p50=0.058s
ssr TTFB_p50=0.533s
ssg TTFB_p50=0.068s
isr TTFB_p50=0.058s
LCP (Largest Contentful Paint)
- 뷰포트 내에서 가장 큰 콘텐츠(보통 이미지나 큰 텍스트 블록)가 화면에 뜨는 시점
→ 사용자 체감 로딩 속도
INP (Interaction to Next Paint)
- 사용자가 클릭/탭/타이핑 등 입력했을 때 화면이 반응해 다시 페인트되는 데 걸린 시간
→ 인터랙션 체감 반응 속도
FID (First Input Delay)
- 사용자가 페이지에서 처음으로 입력(클릭, 탭, 키 입력 등)을 했을 때,
브라우저가 그 입력을 실제로 처리하기 시작하기까지 걸린 시간.
- “페이지가 처음으로 반응할 수 있는 시점”을 보여주는 지표.
정리
- LCP → “언제 보여?” (화면 보이는 속도)
- FID → “첫 입력에 바로 반응해?” (첫 클릭/탭 반응 속도)
- INP → “계속 반응 잘해?” (전체 상호작용 반응성)
- TTFB → “서버에서 얼마나 빨리 응답 시작해?” (네트워크+백엔드 성능)
ㅤ | LCP | INP | TTFB | FID |
csr | 2.40s | 120ms | 0.058s | 25ms |
ssr | 1.10s | 85ms | 0.533s | 18ms |
ssg | 0.60s | 60ms | 0.068s | 12ms |
isr | 0.95s | 75ms | 0.058s | 14ms |
지표가 커질 때 의미하는 것
LCP (Largest Contentful Paint)
- 크면:
- 화면에 표시되는 주요 이미지/텍스트 용량이 크거나 (예: 큰 hero image)
- 네트워크 대역폭이 느리거나
- JS/CSS 파싱·렌더링이 오래 걸려서 콘텐츠가 늦게 그려짐.
- 즉, 리소스 크기 + 네트워크 속도 + 렌더링 성능의 영향을 받음.
INP (Interaction to Next Paint)
- 크면:
- 메인 스레드가 무거운 JS 실행에 막혀 있음 (예: 무거운 연산, 이벤트 핸들러 최적화 안 됨).
- React 같은 프레임워크가 리렌더링을 너무 많이 해서 반응 지연.
- 디바이스 성능이 낮아도 INP가 커질 수 있음.
- 즉, 코드 최적화 부족 + CPU 부하 + JS 블로킹 문제.
FID (First Input Delay)
- 크면:
- 초기 로딩 시 JS 번들이 너무 커서, 브라우저 메인 스레드가 실행 끝날 때까지 입력을 처리 못 함.
- SPA에서 hydration이 길어질 때 흔히 발생.
- 즉, 초기 JS 번들 크기 + hydration 지연 문제.
TTFB (Time To First Byte)
- 크면:
- 서버 응답이 느림 (DB 쿼리 지연, SSR에서 API 호출 지연).
- 네트워크 왕복(RTT)이 멀어서 발생 (예: 한국에서 미국 서버 요청).
- CDN 캐시 미스(cache miss)로 원 서버까지 다녀오는 경우.
- 즉, 네트워크 지연 + 서버 처리 속도 + 캐싱 전략 문제.


