프론트엔드개발자란?
2026년 7월 9일
프론트엔드 개발자란 무엇인가: 화면을 만드는 사람을 넘어, 사용자 경험을 설계하는 사람
프론트엔드 개발자를 아주 단순하게 말하면 “사용자가 보는 화면을 만드는 개발자”라고 할 수 있다.
하지만 실제 서비스 관점에서 보면 이 설명은 너무 좁다.
프론트엔드는 단순히 버튼, 카드, 메뉴, 입력창을 배치하는 사람이 아니다.
사용자가 서비스를 처음 봤을 때 느끼는 첫인상,
버튼을 눌렀을 때 반응하는 속도,
데이터가 늦게 올 때 덜 답답하게 느끼게 만드는 방식,
페이지를 이동할 때 끊기지 않는 느낌,
장애나 로딩 상황에서도 사용자가 현재 상태를 이해할 수 있게 만드는 경험까지 설계한다.
즉 프론트엔드는 사용자가 실제로 제품을 체감하는 영역을 책임지는 개발자다.
1. 사용자는 백엔드 구조를 보지 않는다
서비스를 만드는 입장에서는 서버, DB, 캐시, CDN, API, 인프라 구조가 중요하다.
하지만 일반 사용자는 그런 것들을 보지 않는다.
사용자가 보는 것은 결국 이것이다.
- 화면이 빨리 뜨는가?
- 버튼을 눌렀을 때 바로 반응하는가?
- 로딩 중인지 멈춘 건지 알 수 있는가?
- 페이지 이동이 자연스러운가?
- 데이터가 바뀌었을 때 화면이 즉시 반응하는가?
- 사용하기 편한가?
예를 들어 백엔드에서 API 응답이 2초 걸린다고 해보자.
사용자 입장에서는 그냥 이렇게 느낀다.
“왜 이렇게 느리지?”
이때 백엔드에서 서버 성능을 높이거나, 서버를 여러 대로 늘리거나, CDN을 붙이는 방식도 있다.
하지만 프론트엔드에서도 사용자의 체감 속도를 크게 개선할 수 있다.
예를 들면 다음과 같다.
- 데이터를 기다리는 동안 빈 화면 대신 스켈레톤 UI를 보여준다.
- 버튼 클릭 직후 서버 응답을 기다리지 않고 먼저 화면에 반영하는 옵티미스틱 UI를 적용한다.
- 꼭 필요한 데이터만 먼저 보여주고, 덜 중요한 데이터는 나중에 불러온다.
- 페이지 전체를 새로고침하지 않고 필요한 부분만 바꾼다.
그래서 프론트엔드는 단순히 “예쁜 화면”을 만드는 역할이 아니다.
제품의 첫인상, 체감 속도, 사용자 만족도를 설계하는 결정적인 포지션이다.
2. 최적화는 백엔드만의 일이 아니다
네트워크 레이턴시가 길 때 흔히 떠올리는 해결책은 백엔드 최적화다.
예를 들어 이런 방식들이 있다.
백엔드 최적화
1. 서버 Scale Up
서버 한 대의 성능을 높이는 방식이다.
예를 들어 CPU, 메모리, 디스크 성능이 더 좋은 서버로 바꾸는 것이다.
장점은 단순하다.
서버 자체가 더 강해지므로 더 많은 요청을 처리할 수 있다.
하지만 단점도 있다.
성능 좋은 서버는 비싸고, 어느 순간부터는 더 올리기 어렵다.
2. 서버 Scale Out
서버 한 대를 강하게 만드는 대신, 서버 여러 대를 두는 방식이다.
예를 들어 서버가 한 대일 때는 모든 사용자의 요청이 한 서버로 몰린다.
사용자들 → 서버 1대
Scale Out을 하면 이렇게 된다.
사용자들 → 로드밸런서 → 서버 1
→ 서버 2
→ 서버 3
요청을 여러 서버로 나눠 처리하므로 트래픽을 더 잘 버틸 수 있다.
하지만 서버가 늘어나면 운영 비용도 늘어나고, 배포/모니터링/장애 대응도 복잡해진다.
3. CDN 도입
CDN은 정적 리소스를 사용자와 가까운 위치에서 제공하는 방식이다.
이미지, CSS, JS 파일 같은 정적 리소스를 원 서버에서 매번 가져오지 않고,
사용자 근처의 CDN 서버에서 내려주면 더 빠르게 받을 수 있다.
예를 들어 한국 사용자가 미국 서버에서 이미지를 받는 것보다,
한국이나 일본 근처의 CDN 서버에서 받는 편이 훨씬 빠르다.
4. 캐싱 전략
캐싱은 자주 쓰는 데이터를 매번 새로 계산하거나 조회하지 않고 저장해두는 방식이다.
예를 들어 인기 게시글 목록이 1분마다만 바뀌어도 충분하다면,
사용자가 요청할 때마다 DB를 조회하지 않고 1분 동안 캐시된 결과를 내려줄 수 있다.
장점은 서버 부하가 줄고 응답 속도가 빨라진다는 것이다.
그런데 백엔드 최적화에는 비용이 든다
백엔드 최적화는 강력하지만 대부분 비용이 증가한다.
- 서버 성능을 올리면 서버비가 증가한다.
- 서버 개수를 늘리면 서버비가 증가한다.
- CDN을 쓰면 트래픽 비용이 발생한다.
- 캐싱 시스템을 두면 인프라와 운영 복잡도가 올라간다.
그래서 모든 문제를 백엔드에서만 해결하려고 하면 비싸고 복잡해질 수 있다.
이때 프론트엔드 최적화가 중요해진다.
3. 프론트엔드 최적화: 실제 속도와 체감 속도
프론트엔드 최적화는 크게 두 가지로 볼 수 있다.
1. 실제로 빠르게 만들기
2. 사용자가 빠르다고 느끼게 만들기
둘 다 중요하다.
3-1. 스켈레톤 로딩
데이터가 오기 전까지 빈 화면을 보여주면 사용자는 불안해진다.
하얀 화면
...
...
아무것도 안 보임
이러면 사용자는 서비스가 멈춘 것처럼 느낀다.
스켈레톤 UI는 실제 데이터가 들어올 자리에 회색 박스 같은 임시 구조를 보여주는 방식이다.
[ 제목 자리 ]
[ 이미지 자리 ]
[ 본문 자리 ]
사용자는 이것을 보고 이렇게 생각한다.
“아, 지금 뭔가 불러오는 중이구나.”
중요한 점은 스켈레톤 UI가 API 응답 자체를 빠르게 만드는 것은 아니라는 것이다.
실제 네트워크 시간이 2초라면 2초는 그대로 걸릴 수 있다.
하지만 사용자가 느끼는 답답함은 줄어든다.
즉 스켈레톤 UI는 실제 속도 개선이라기보다 체감 속도 개선에 가깝다.
3-2. 옵티미스틱 UI
옵티미스틱 UI는 사용자의 행동이 성공할 것이라고 가정하고,
서버 응답이 오기 전에 먼저 화면을 바꾸는 방식이다.
예를 들어 좋아요 버튼을 눌렀다고 하자.
일반적인 방식은 이렇다.
1. 사용자가 좋아요 클릭
2. 서버에 요청
3. 서버가 성공 응답
4. 화면의 좋아요 숫자 증가
이 경우 서버 응답이 느리면 사용자는 버튼을 눌렀는데 반응이 없는 것처럼 느낀다.
옵티미스틱 UI는 다르게 동작한다.
1. 사용자가 좋아요 클릭
2. 화면에서 좋아요 숫자를 먼저 증가시킴
3. 서버에 요청
4. 성공하면 그대로 둠
5. 실패하면 다시 원래대로 되돌림
사용자 입장에서는 버튼이 즉시 반응하므로 서비스가 훨씬 빠르게 느껴진다.
다만 주의할 점도 있다.
서버 요청이 실패했을 때 롤백 처리를 잘 해야 한다.
예를 들어 좋아요 요청이 실패했는데 화면에는 계속 좋아요가 반영되어 있으면
실제 서버 상태와 화면 상태가 달라진다.
그래서 옵티미스틱 UI는 UX를 좋게 만들 수 있지만, 상태 관리가 중요하다.
4. 웹의 시작: 정적 HTML 시대
초기 웹은 지금처럼 복잡하지 않았다.
가장 단순한 방식은 HTML 파일을 여러 개 만들어두고,
사용자가 링크를 클릭하면 해당 HTML 파일을 서버에서 받아오는 구조였다.
예를 들어 파일이 이렇게 있다고 해보자.
/index.html
/about.html
/product.html
/contact.html
HTML 안에는 이런 링크가 있다.
<a href="/about.html">소개 페이지</a> <a href="/product.html">상품 페이지</a>
사용자가 소개 페이지를 클릭하면 브라우저는 서버에 요청한다.
브라우저 → 서버: /about.html 주세요
서버 → 브라우저: about.html 응답
브라우저: 기존 화면 버리고 새 HTML 렌더링
이 방식은 단순하고 이해하기 쉽다.
하지만 문제가 있다.
모든 페이지가 미리 만들어져 있어야 한다.
날씨, 뉴스, 게시판, 검색 결과처럼 매번 내용이 바뀌는 페이지에는 적합하지 않다.
5. URL 요청은 실제로 무엇을 요청하는 걸까?
브라우저 주소창에 URL을 입력하면 단순히 “페이지 이름”을 입력하는 것 같지만,
실제로는 서버에게 특정 리소스를 요청하는 것이다.
예를 들어 다음 주소가 있다고 해보자.
http://google.com/search
브라우저는 먼저 google.com이라는 도메인을 IP 주소로 바꾼다.
이 작업을 DNS 조회라고 한다.
google.com → 172.xxx.xxx.xxx
컴퓨터를 구분하는 것은 IP 주소다.
하지만 한 컴퓨터 안에서도 여러 프로그램이 실행될 수 있다.
예를 들어 한 서버 컴퓨터 안에 이런 프로그램들이 있을 수 있다.
80번 포트 → 웹 서버
3306번 포트 → MySQL
6379번 포트 → Redis
그래서 포트 번호는 컴퓨터 안에서 어떤 프로그램으로 보낼지 구분하는 번호라고 보면 된다.
HTTP는 기본적으로 80번 포트를 사용하고, HTTPS는 기본적으로 443번 포트를 사용한다.
baseDir 개념
서버는 보통 특정 폴더를 기준으로 파일을 찾는다.
예를 들어 서버의 baseDir가 다음과 같다고 하자.
C:/workspaces/soma
사용자가 이렇게 요청하면:
GET /document.docx
서버는 실제로 이런 파일을 찾는다.
C:/workspaces/soma/document.docx
사용자가 세부 경로 없이 /만 요청하면 보통 기본 파일을 내려준다.
GET /
그러면 서버는 보통 다음 파일을 찾는다.
C:/workspaces/soma/index.html
그래서 웹 서버에서 index.html은 기본 진입 파일처럼 쓰인다.
Content-Type의 의미
서버 응답에는 보통 Content-Type이라는 헤더가 있다.
예를 들어:
Content-Type: text/html
이 말은 서버가 브라우저에게 이렇게 알려주는 것이다.
“이 응답은 HTML 문서로 해석해.”
사실 HTTP 응답의 본문은 결국 문자열 또는 바이트 데이터다.
브라우저가 그것을 HTML로 읽을지, JSON으로 읽을지, 이미지로 읽을지는 Content-Type 같은 정보를 보고 판단한다.
예를 들어:
Content-Type: text/html
이면 HTML 문서로 해석한다.
Content-Type: application/json
이면 JSON 데이터로 해석한다.
Content-Type: image/png
이면 PNG 이미지로 해석한다.
6. 정적 리소스와 동적 리소스
웹 요청은 크게 정적 리소스 요청과 동적 리소스 요청으로 나눠볼 수 있다.
정적 리소스
정적 리소스는 서버에 이미 존재하는 파일을 그대로 내려주는 것이다.
예를 들어:
/logo.png
/style.css
/app.js
/about.html
이런 파일들은 보통 서버가 특별한 계산을 하지 않고 그대로 내려준다.
브라우저: /logo.png 주세요
서버: 저장되어 있던 logo.png 그대로 줄게
이게 정적 리소스다.
동적 리소스
동적 리소스는 요청이 들어올 때 서버가 계산하거나 DB를 조회해서 응답을 만들어내는 것이다.
예를 들어 사용자가 날씨를 검색한다고 하자.
GET /weather?region=busan
서버는 단순히 weather.html 파일 하나를 그대로 주는 게 아니다.
1. region=busan 확인
2. 부산 날씨 데이터 조회
3. HTML 또는 JSON 응답 생성
4. 브라우저로 응답
결과로 내려온 HTML이 이렇게 생겼다고 해보자.
<h1>부산 날씨</h1> <p>현재 기온: 24도</p>
겉으로 보기에는 HTML이니까 정적인 것처럼 보일 수 있다.
하지만 이 HTML은 서버가 요청 시점에 데이터를 조회해서 만든 것이다.
그래서 동적 리소스다.
핵심은 이거다.
응답 결과가 HTML이냐 JSON이냐가 중요한 게 아니라, 서버가 요청 시점에 계산해서 만들었느냐가 중요하다.
7. SST: Server Side Template이 왜 나왔을까?
초기 정적 HTML 방식은 모든 페이지를 미리 만들어둬야 했다.
하지만 현실의 서비스는 그렇게 단순하지 않다.
예를 들어 게시판 목록을 생각해보자.
/board
이 페이지는 사용자들이 글을 쓰면 계속 바뀐다.
정적 HTML 방식이라면 게시글이 하나 추가될 때마다
board.html 파일을 사람이 직접 수정해야 한다.
말이 안 된다.
그래서 서버가 DB에서 데이터를 가져와 HTML에 끼워 넣는 방식이 필요해졌다.
이게 Server Side Template, 즉 SST다.
SST는 “진짜 실시간 통신”이 아니다
강의에서 “실시간 때문에 SST가 나왔다”는 식의 표현이 있었다면 조금 헷갈릴 수 있다.
여기서 말하는 실시간은 WebSocket 채팅이나 실시간 알림 같은 의미가 아니다.
정확히는 다음 의미에 가깝다.
요청 시점마다 달라지는 최신 데이터를 HTML에 반영해야 했다.
예를 들어 날씨 페이지가 있다고 하자.
/weather?city=busan
서버는 요청이 들어올 때마다 날씨 데이터를 조회하고,
그 값을 HTML에 넣어서 응답한다.
템플릿은 이런 식이다.
<h1><%= city %> 날씨</h1> <p>현재 기온: <%= temperature %>도</p> <p>상태: <%= status %></p>
서버가 데이터를 채우면 최종 HTML은 이렇게 된다.
<h1>부산 날씨</h1> <p>현재 기온: 24도</p> <p>상태: 흐림</p>
브라우저는 최종 HTML만 받는다.
즉 SST의 핵심은:
서버가 데이터를 조회한다
→ HTML 템플릿에 데이터를 끼워 넣는다
→ 완성된 HTML을 브라우저에 보낸다
이다.
JSP, PHP, ASP 같은 기술들
JSP, PHP, ASP 같은 기술들은 대표적인 서버 사이드 템플릿 방식이다.
예를 들어 JSP에서는 HTML 안에 Java 코드를 섞어서 서버에서 HTML을 생성할 수 있었다.
흐름은 다음과 같다.
브라우저 → 서버: /board 요청
서버 → DB 조회
서버 → board.jsp 템플릿에 데이터 삽입
서버 → 완성된 HTML 응답
브라우저 → HTML 렌더링
이 방식은 정적 HTML보다 훨씬 강력했다.
게시판, 로그인, 검색 결과, 마이페이지 같은 동적 화면을 만들 수 있었기 때문이다.
-
그럼 SST는 프론트엔드와 백엔드가 분리되지 않은 시대인가요?
맞아. SST(Server Side Template) 시절에는 지금처럼 “프론트엔드 앱”과 “백엔드 API 서버”가 명확히 분리되어 있지 않은 경우가 많았어.
정확히 말하면:
화면을 만드는 코드와 서버 로직이 같은 서버 프로젝트 안에 같이 있었다.
예를 들어 JSP로 게시판을 만든다고 하면 구조가 대충 이랬어.
Java 서버 프로젝트 ├── Controller / Servlet │ └── 게시글 목록 요청 처리 ├── Service │ └── 게시글 비즈니스 로직 ├── Repository / DAO │ └── DB 조회 └── JSP └── HTML 템플릿사용자가
/board.do로 요청하면:1. 브라우저가 서버에 /board.do 요청 2. Java Controller가 요청 받음 3. DB에서 게시글 목록 조회 4. JSP 파일에 게시글 데이터를 넣음 5. 완성된 HTML을 브라우저로 보냄즉 JSP 파일은 이런 느낌이야.
<h1>게시판</h1> <c:forEach var="post" items="${posts}"> <div> <h2>${post.title}</h2> <p>${post.content}</p> </div> </c:forEach>여기서 HTML도 있고, 서버에서 내려준 데이터도 들어가 있지?
그래서 이때의 “프론트엔드”는 지금처럼 React 앱을 따로 만드는 게 아니라, 서버 프로젝트 안에 있는 JSP/PHP/ASP 템플릿을 작성하는 영역에 가까웠어.
요즘 방식과 비교하면 이렇게 보면 돼.
예전 SST / JSP 방식 브라우저 ↓ 요청 백엔드 서버 ├── DB 조회 ├── 서버 로직 실행 ├── JSP/템플릿으로 HTML 생성 └── 완성된 HTML 응답요즘 React + API 방식 브라우저 ├── React 앱 실행 ├── 화면 렌더링 └── API 요청 ↓ 백엔드 서버 ├── DB 조회 └── JSON 응답예전에는 서버가 HTML까지 만들어줬고,
요즘 SPA에서는 백엔드는 주로 JSON만 주고, 브라우저의 React가 HTML 화면을 만든다고 보면 돼.
그래서 질문에 답하면:
SST는 백엔드와 프론트엔드가 한 프로젝트, 한 서버 안에 붙어 있던 구조에 가깝다.
다만 “프론트엔드가 아예 없었다”는 뜻은 아니야.
HTML, CSS, JS는 당연히 있었어.
근데 그 HTML이 React처럼 독립된 프론트엔드 앱에서 만들어지는 게 아니라, 백엔드 서버의 템플릿 파일에서 만들어졌다는 게 핵심이야.
한 문장으로 정리하면:
SST는 백엔드 서버가 DB 조회부터 HTML 생성까지 다 맡던 방식이고, 이때의 프론트엔드는 백엔드 프로젝트 안의 템플릿 영역에 가까웠다.
8. Apache와 Tomcat은 무슨 관계일까?
Java 웹 환경에서 자주 나오는 이름이 Apache와 Tomcat이다.
정확히 나누면 보통 이렇게 이해할 수 있다.
Apache HTTP Server
Apache HTTP Server는 전통적인 웹 서버다.
주로 정적 파일을 잘 내려준다.
HTML
CSS
JS
이미지
파일
이런 것들을 처리하는 데 사용된다.
Tomcat
Tomcat은 Java Servlet/JSP를 실행할 수 있는 서버다.
즉 Java 기반의 동적 웹 요청을 처리한다.
예를 들어 .jsp, .do 같은 요청을 처리할 수 있다.
브라우저 → /login.do 요청
Tomcat → Java 코드 실행
Tomcat → DB 조회
Tomcat → HTML 응답 생성
Tomcat은 흔히 “서블릿 컨테이너” 또는 “서블릿 엔진”이라고 부른다.
.do는 뭘까?
예전 Java 웹 서비스에서 .do는 동적 요청을 나타내는 URL 패턴으로 많이 쓰였다.
예를 들어:
/login.do
/boardList.do
/memberJoin.do
이런 URL은 보통 정적 파일을 의미하는 게 아니라,
서버의 Java 코드로 연결되는 요청이다.
즉 사용자가 /login.do를 요청하면 서버는 단순히 login.do라는 파일을 찾는 게 아니라,
해당 요청을 처리할 Controller나 Servlet 코드를 실행한다.
흐름은 보통 이렇다.
브라우저 → /login.do 요청
Tomcat → LoginController 실행
서버 → DB 확인
서버 → 결과 HTML 또는 redirect 응답
그래서 .do가 붙어 있으면 보통 전통적인 Java 기반 MPA/SST 서비스일 가능성이 있다.
9. MPA: Multi Page Application
정적 HTML, JSP, PHP, ASP 같은 전통적인 웹은 대부분 MPA 방식이었다.
MPA는 Multi Page Application의 약자다.
말 그대로 여러 페이지로 구성된 애플리케이션이다.
예를 들어:
/login
/board
/mypage
/product
각 경로로 이동할 때마다 서버에 새 요청을 보내고,
서버는 새 HTML을 내려준다.
/login 페이지
→ /board 페이지 이동
→ 서버에 /board 요청
→ board HTML 응답
→ 브라우저가 새로 렌더링
이 방식의 특징은 페이지를 이동할 때마다 브라우저가 새 문서를 받는다는 것이다.
MPA의 문제: 상태가 유지되지 않는다
전통적인 MPA에서는 페이지를 이동하면 기존 페이지의 JavaScript 메모리가 사라진다.
-
자바스크립트 메모리가 뭔데?
여기서 말하는 JavaScript 메모리는 어렵게 말하면 “브라우저에서 실행 중인 JS 코드가 잠깐 기억하고 있는 값들”이야.
쉽게 말하면:
페이지가 켜져 있는 동안 JS가 머릿속에 들고 있는 변수, 상태, 객체들
이라고 보면 돼.
예를 들어 이런 코드가 있다고 해보자.
let count = 0; count = count + 1; console.log(count); // 1여기서
count값1은 어디에 저장돼 있을까?파일에 저장된 것도 아니고, DB에 저장된 것도 아니고, 서버에 저장된 것도 아니야.
브라우저가 이 페이지를 실행하는 동안 메모리에 잠깐 들고 있는 값이야.
이게 JavaScript 메모리 상태야.
React로 보면 더 익숙해져.
function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> {count} </button> ); }사용자가 버튼을 눌러서
count가 5가 됐다고 해보자.count = 5이 값은 어디에 있냐?
브라우저의 JavaScript 메모리 안에 있어.
즉 React의
useState, Redux, Zustand 같은 것도 결국 브라우저 메모리에 값을 들고 있는 거야.
문제는 페이지를 새로 이동하면 이 메모리가 사라진다는 거야.
전통적인 MPA에서는
/page1에서/page2로 이동할 때 이런 일이 일어나.1. 현재 페이지 종료 2. 현재 페이지에서 실행 중이던 JS도 종료 3. JS가 들고 있던 변수, state, 객체들도 사라짐 4. 서버에서 새 HTML 받음 5. 새 페이지의 JS가 처음부터 다시 실행됨그러니까
/page1에서 이런 상태가 있었다고 해보자.let inputValue = "도완"; let selectedTab = "인기글"; let count = 5;그런데 전통적인 방식으로
/page2로 이동하면:/page1의 JS 실행 환경 종료 → inputValue 사라짐 → selectedTab 사라짐 → count 사라짐이렇게 되는 거야.
비유하면 이거야.
MPA는 페이지 이동할 때마다 노트북을 껐다 켜는 것에 가까워.
/page1에서 메모장에 count=5 적어둠 → /page2로 이동 → 컴퓨터 꺼졌다 켜짐 → 메모장 내용 사라짐반면 SPA는 앱이 계속 켜져 있는 상태에서 화면만 바꾸는 느낌이야.
React 앱은 계속 실행 중 → /home 화면에서 /about 화면으로 컴포넌트만 교체 → 메모리에 있던 상태는 유지 가능그래서 SPA에서는 Redux/Zustand 같은 상태가 유지될 수 있어.
정리하면 이거야.
JavaScript 메모리 = 브라우저에서 JS가 실행되는 동안 잠깐 들고 있는 값들예를 들면:
let 변수 useState 값 Redux store 값 Zustand store 값 현재 입력 중인 input 값 선택한 탭 상태 모달 열림 여부 필터 조건 스크롤 위치 같은 임시 상태이런 것들.
전통적인 MPA에서는 페이지 이동 시 새 HTML을 받으면서 기존 JS 실행 환경이 끝나기 때문에 이 값들이 사라진다.
SPA에서는 페이지 전체가 새로고침되지 않고 하나의 JS 앱이 계속 살아 있으므로, 이런 메모리 상태를 유지할 수 있다.
예를 들어 로그인 폼이 여러 단계로 나뉘어 있다고 하자.
1단계: 아이디 입력
2단계: 개인정보 입력
3단계: 약관 동의
1단계에서 사용자가 입력한 값이 브라우저 메모리에만 있었다면,
2단계로 이동하면서 새 HTML을 받는 순간 그 메모리는 날아간다.
그래서 전통적인 방식에서는 상태를 유지하기 위해 여러 방법을 사용했다.
- 서버 세션에 저장
- 쿠키에 저장
- hidden input으로 같이 넘김
- URL query string에 넣음
- localStorage 사용
10. hidden input과 ViewState
전통적인 웹에서는 상태를 유지하기 위해 hidden input을 많이 사용했다.
hidden input은 화면에는 보이지 않지만, form을 제출할 때 서버로 함께 전송되는 input이다.
<input type="hidden" name="userId" value="123"/>
사용자는 이 input을 화면에서 볼 수 없다.
하지만 form을 제출하면 서버로 같이 넘어간다.
userId=123
ASP.NET의 __VIEWSTATE
ASP.NET Web Forms에서는 __VIEWSTATE라는 hidden input을 사용했다.
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="엄청 긴 문자열..." />
이 긴 문자열 안에는 페이지의 상태 정보가 들어 있다.
예를 들어:
선택한 탭: 인기글
체크박스 상태: 체크됨
드롭다운 선택값: 부산
페이지 번호: 3
버튼 활성화 여부: true
이런 상태를 서버가 긴 문자열로 인코딩해서 HTML 안에 숨겨둔다.
그리고 사용자가 form을 제출하면 이 값이 서버로 다시 전송된다.
브라우저 → 서버:
region=busan
__VIEWSTATE=긴상태문자열
서버는 이 값을 해석해서 이전 페이지 상태를 복원한다.
왜 이런 방식이 무거웠을까?
상태가 많아질수록 __VIEWSTATE 값도 길어진다.
즉 매번 이런 일이 발생한다.
서버 → 브라우저: HTML + 긴 ViewState
브라우저 → 서버: form 데이터 + 긴 ViewState
서버 → 브라우저: 새 HTML + 새 ViewState
상태를 유지하기 위해 매번 큰 문자열을 주고받는 것이다.
현대 React 방식과 비교하면 차이가 크다.
React에서는 보통 상태를 브라우저의 JavaScript 메모리에서 관리한다.
const [selectedTab, setSelectedTab] = useState("popular"); const [region, setRegion] = useState("busan");
즉 상태가 컴포넌트 안에 있다.
반면 ViewState 방식은 이런 느낌이다.
상태를 긴 문자열로 HTML에 숨김
→ 다음 요청 때 서버로 다시 보냄
→ 서버가 복원함
요즘 프론트엔드 관점에서는 무겁고 답답한 방식이다.
11. AJAX: 페이지 전체가 아니라 일부만 바꾸자
MPA의 문제는 페이지를 이동하거나 데이터를 갱신할 때마다
전체 HTML을 다시 받아와야 한다는 점이었다.
예를 들어 게시판에서 “다음 페이지”를 누를 때마다 전체 HTML이 새로 로드되면 비효율적이다.
AJAX는 이 문제를 완화했다.
AJAX를 사용하면 페이지 전체를 새로고침하지 않고도 서버에 데이터를 요청할 수 있다.
브라우저 → 서버: 게시글 목록 JSON 주세요
서버 → 브라우저: JSON 응답
브라우저 → 현재 페이지 안의 테이블만 업데이트
예를 들어 테이블 데이터만 바꾸는 것은 AJAX로 가능하다.
fetch("/api/posts") .then((res) => res.json()) .then((data) => { // 테이블만 다시 그림 });
하지만 AJAX만으로는 페이지 이동 문제가 완전히 해결되지 않았다
AJAX는 현재 페이지 안의 일부 데이터를 바꾸는 데는 좋았다.
예를 들어:
- 테이블 갱신
- 댓글 더보기
- 좋아요 처리
- 검색 자동완성
- 일부 영역 새로고침
이런 것은 가능했다.
하지만 메뉴 이동 자체는 여전히 전통적인 페이지 이동인 경우가 많았다.
/home → /board → /mypage
이동할 때마다 서버에서 새 HTML을 받아오는 구조였다.
즉 AJAX는 “부분 업데이트”는 가능하게 했지만,
“앱 전체를 하나의 상태 기반 UI로 운영하는 방식”까지는 아니었다.
이 문제를 더 크게 해결한 흐름이 SPA다.
12. SPA: Single Page Application
SPA는 Single Page Application의 약자다.
말 그대로 HTML 문서 하나를 기반으로 동작하는 웹 애플리케이션이다.
React, Vue, Angular 같은 프레임워크가 대표적으로 SPA 구조를 많이 사용한다.
SPA는 처음에 HTML 하나를 받는다
React SPA의 기본 HTML은 보통 이렇게 생겼다.
<div id="root"></div> <script src="/src/main.jsx"></script>
처음에는 거의 빈 HTML이다.
화면을 직접 그리는 것은 HTML이 아니라 JavaScript다.
브라우저가 HTML 받음
→ JS 파일 다운로드
→ React 실행
→ 현재 URL에 맞는 컴포넌트 렌더링
예를 들어 React 앱 안에 이런 라우팅 설정이 있다고 하자.
<Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/board" element={<Board />} /> </Routes>
그러면 React Router는 현재 URL을 보고 어떤 컴포넌트를 보여줄지 결정한다.
현재 URL이 /about
→ About 컴포넌트 렌더링
“HTML 하나인데 모든 페이지를 어떻게 보여주냐?”
처음 들으면 이상하다.
HTML이 하나라면서
/home,/about,/board는 어떻게 다르게 보여주지?
정답은 이것이다.
서버는 거의 빈 HTML과 JS 앱을 내려준다.
브라우저 안의 JS가 현재 URL을 보고 알맞은 컴포넌트를 렌더링한다.
즉 서버가 /about.html을 따로 주는 게 아니다.
브라우저 안에서 실행 중인 React 앱이:
URL이 /about이네?
→ About 컴포넌트를 보여줘야겠다.
하고 판단하는 것이다.
13. window.history와 React Router
SPA에서 페이지 이동은 실제 페이지 이동이 아니다.
전통적인 웹에서 페이지 이동은 서버 요청이었다.
/about 클릭
→ 서버에 /about 요청
→ about.html 응답
→ 브라우저가 새 페이지 렌더링
하지만 SPA에서는 내부 링크를 클릭해도 보통 서버에 새 HTML을 요청하지 않는다.
대신 window.history.pushState()를 사용해서 주소창만 바꾼다.
window.history.pushState(null, "", "/about");
이 코드는 주소창을 /about으로 바꾸지만,
서버에 /about HTML을 새로 요청하지 않는다.
그럼 화면은 누가 바꾸나?
React Router가 바꾼다.
React Router는 현재 URL을 보고 있다가, URL이 바뀌면 해당 컴포넌트를 렌더링한다.
1. 사용자가 About 클릭
2. React Router가 기본 a 태그 이동을 막음
3. window.history로 URL만 /about으로 바꿈
4. React Router가 URL 변화를 감지
5. /about에 맞는 About 컴포넌트 렌더링
이때 페이지 전체 새로고침은 발생하지 않는다.
일반 a 태그와 Link 차이
일반 a 태그는 브라우저의 기본 이동을 발생시킨다.
<a href="/about">About</a>
흐름은 이렇다.
서버에 /about 요청
→ 새 HTML 받음
→ 전체 새로고침
React Router의 Link는 다르다.
<Link to="/about">About</Link>
흐름은 이렇다.
기본 페이지 이동 막음
→ URL만 바꿈
→ React가 컴포넌트만 교체
그래서 SPA에서는 내부 페이지 이동에 보통 a 태그 대신 Link를 쓴다.
14. Redux, Zustand가 SPA에서 힘을 발휘하는 이유
Redux나 Zustand는 브라우저 안에서 상태를 저장하는 도구다.
예를 들어 이런 상태를 저장할 수 있다.
{ user: { name: "도완" }, selectedTeam: "Tottenham", cart: [1, 2, 3], isLoginModalOpen: false }
SPA에서는 페이지를 이동해도 앱 전체가 새로고침되지 않는다.
/home
→ /board
→ /mypage
겉으로는 페이지가 바뀌지만 실제로는 하나의 React 앱이 계속 살아 있고,
컴포넌트만 바뀐다.
그래서 Redux나 Zustand에 저장된 메모리 상태가 유지된다.
/home에서 로그인 정보 저장
→ /board로 이동
→ 새로고침 없음
→ 상태 유지
반면 전통적인 MPA에서는 페이지를 이동할 때마다 새 HTML을 받으므로
기존 JS 메모리가 사라진다.
/home
→ /board로 실제 이동
→ 기존 JS 앱 종료
→ 메모리 상태 사라짐
물론 MPA에서도 쿠키, 서버 세션, localStorage 등을 사용하면 상태를 유지할 수 있다.
하지만 JS 메모리 상태는 기본적으로 유지되지 않는다.
이 차이 때문에 SPA에서는 클라이언트 상태 관리 라이브러리가 강력하게 활용된다.
15. SPA의 단점: 첫 렌더링까지 최소 두 번의 왕복이 필요할 수 있다
SPA는 페이지 이동이 부드럽고 상태 유지가 쉽다.
하지만 단점도 있다.
처음 화면을 보기까지 과정이 길어질 수 있다.
CSR 방식의 React SPA를 생각해보자.
1. 브라우저 → 서버: HTML 주세요
2. 서버 → 브라우저: 빈 HTML + JS 파일 링크 응답
3. 브라우저 → 서버: JS 파일 주세요
4. 서버 → 브라우저: JS 파일 응답
5. 브라우저: JS 실행
6. 브라우저 → 서버: API 데이터 주세요
7. 서버 → 브라우저: JSON 응답
8. 브라우저: 화면 렌더링
처음 받은 HTML은 거의 비어 있다.
<div id="root"></div> <script src="/app.js"></script>
그래서 사용자가 실제 화면을 보기 위해서는 JS를 받고, 실행하고, API 데이터까지 받아야 한다.
이 때문에 “최소 두 번 왕복해야 렌더링된다”는 말이 나온다.
정확히는 서비스 구조에 따라 다르지만, CSR에서는 보통:
HTML 받기
→ JS 받기
→ API 데이터 받기
→ 렌더링
이런 흐름이 생긴다.
왜 왕복이 문제일까?
컴퓨터에서 속도는 대략 이런 느낌으로 차이가 난다.
CPU > Memory > Disk > Network I/O
네트워크 I/O는 특히 느리고 불안정하다.
사용자의 위치, 네트워크 상태, 서버 위치, 패킷 손실, 모바일 환경 등에 따라 시간이 크게 달라진다.
그래서 웹 성능에서는 통신 횟수를 줄이는 것이 중요하다.
SPA는 내부 이동에서는 효율적이지만,
첫 진입에서는 빈 HTML → JS → API 데이터의 흐름 때문에 초기 렌더링이 느려질 수 있다.
16. SSR: Server Side Rendering
SSR은 Server Side Rendering의 약자다.
말 그대로 서버에서 HTML을 미리 렌더링해서 브라우저에 보내는 방식이다.
SPA의 CSR 방식에서는 브라우저가 JS를 실행해야 화면이 만들어진다.
반면 SSR은 서버가 먼저 HTML을 만들어준다.
브라우저 → 서버: /product/1 페이지 주세요
서버 → 데이터 조회
서버 → React 컴포넌트를 HTML로 렌더링
서버 → 완성된 HTML 응답
브라우저 → HTML을 바로 표시
즉 브라우저는 JS가 완전히 실행되기 전에도 화면의 기본 내용을 볼 수 있다.
SSR과 SST는 비슷한가?
겉으로 보면 비슷하다.
둘 다 서버에서 HTML을 만들어서 브라우저로 보낸다.
하지만 차이가 있다.
SST
JSP, PHP, ASP 같은 템플릿 기반 방식이다.
HTML 템플릿 + 서버 데이터 = 최종 HTML
SSR
React, Vue 같은 컴포넌트 기반 UI를 서버에서 HTML로 렌더링하는 방식이다.
React 컴포넌트 + 서버 데이터 = 최종 HTML
즉 둘 다 서버에서 HTML을 만든다는 점은 같다.
다만 SST는 전통적인 템플릿 중심이고,
SSR은 현대 프론트엔드 컴포넌트 구조를 서버에서도 실행한다는 차이가 있다.
17. Hydration: 서버 HTML에 React가 붙는 과정
SSR에서 중요한 개념이 Hydration이다.
서버가 HTML을 만들어서 브라우저에 보내면,
브라우저는 그 HTML을 먼저 화면에 보여준다.
하지만 이 HTML은 아직 완전한 React 앱처럼 동작하지 않는다.
버튼 클릭, 상태 변경, 이벤트 처리 등을 하려면 브라우저에서 React JavaScript가 실행되어야 한다.
Hydration은 쉽게 말해:
서버가 보내준 HTML에
브라우저의 React 앱이 이벤트와 상태를 연결하는 과정
이다.
예를 들어 서버가 이런 HTML을 보냈다고 하자.
<button>좋아요</button>
브라우저는 버튼을 보여줄 수 있다.
하지만 React가 아직 붙지 않았다면 클릭 이벤트가 제대로 동작하지 않을 수 있다.
JS가 로드되고 React가 실행되면서:
이 버튼은 LikeButton 컴포넌트였구나.
클릭하면 setState를 실행해야 하는구나.
하고 연결한다.
이 과정이 Hydration이다.
18. 현대 웹: SSR과 SPA를 섞어서 쓴다
요즘 Next.js 같은 프레임워크는 SSR과 SPA를 섞어서 사용한다.
흐름은 보통 이렇다.
첫 진입:
서버가 HTML을 만들어서 보냄 → SSR
그 이후 이동:
브라우저 안의 라우터가 URL만 바꿈
필요한 JSON 데이터만 요청
React가 화면만 교체 → SPA처럼 동작
이 방식은 SSR의 장점과 SPA의 장점을 섞는다.
예시: 토스증권 같은 서비스
사용자가 토스증권의 삼성전자 페이지에 처음 들어간다고 해보자.
/stocks/A005930
첫 진입은 서버 요청이다.
브라우저 → 서버: 삼성전자 페이지 주세요
서버 → 삼성전자 데이터 조회
서버 → 삼성전자 HTML 생성
서버 → HTML 응답
브라우저 → 화면 표시
이게 SSR이다.
그런데 사용자가 그 페이지 안에서 카카오 종목을 클릭한다고 하자.
/stocks/A005930
→ /stocks/A035720
겉으로는 URL이 바뀌고 페이지 이동처럼 보인다.
하지만 내부적으로는 새 HTML 문서를 받지 않을 수 있다.
React Router 또는 Next Router가 이동 가로챔
→ URL만 변경
→ 카카오 데이터 JSON 요청
→ 받은 JSON으로 화면 변경
즉 첫 진입은 SSR이고, 이후 이동은 SPA처럼 동작한다.
“URL이 요청하는 것 같지만 JSON만 요청한다”는 말
이 표현의 의미는 이것이다.
전통적인 웹이라면 /stocks/A035720으로 이동할 때 서버에서 HTML 전체를 다시 받아온다.
브라우저 → 서버: /stocks/A035720 HTML 주세요
서버 → 브라우저: HTML 전체 응답
현대 SSR + SPA 구조에서는 이후 이동 때 HTML 전체를 다시 받지 않고,
필요한 데이터만 요청할 수 있다.
브라우저 → 서버: 카카오 주식 데이터 JSON 주세요
서버 → 브라우저: JSON 응답
브라우저 → React가 화면 변경
이런 의미에서 “JSON만 요청해서 SPA처럼 바꾼다”고 말하는 것이다.
19. Next.js에서 라우팅과 요청 경로
Next.js 같은 프레임워크는 사용자가 페이지를 이동할 때 내부적으로 많은 처리를 한다.
예를 들어 URL을 정규화하거나, locale, basePath, trailing slash 등을 처리할 수 있다.
강의에서 나온 skipMiddlewareUrlNormalize 같은 설정은
Next.js의 Middleware 단계에서 URL 정규화 동작을 건너뛰고 실제 요청 경로를 더 원본에 가깝게 확인하고 싶을 때 사용하는 옵션이다.
이건 초보자 입장에서는 당장 깊게 외울 필요는 없다.
중요한 것은 이것이다.
현대 프레임워크는 사용자가 입력한 URL을 그대로 단순 처리하지 않고,
라우팅, 정규화, 미들웨어, 데이터 요청 등을 내부적으로 조합해서 처리한다.
그래서 Network 탭을 보면 사용자가 페이지를 이동한 것처럼 보여도
실제로는 HTML 요청이 아니라 JSON 요청이 발생하는 경우가 있다.
20. Pre-rendering: 미리 HTML을 만들어두기
SSR은 요청이 들어올 때마다 서버가 HTML을 만든다.
사용자 요청
→ 서버가 그때 데이터 조회
→ HTML 생성
→ 응답
그런데 매번 서버에서 HTML을 만들면 비용이 든다.
서버 CPU도 쓰고, 데이터 조회도 하고, 응답 시간도 길어질 수 있다.
그래서 자주 바뀌지 않는 페이지는 미리 HTML을 만들어둘 수 있다.
이걸 보통 Pre-rendering 또는 Static Generation이라고 부른다.
예를 들어 블로그 글, 문서 페이지, 회사 소개 페이지처럼 자주 바뀌지 않는 페이지는
빌드 시점에 HTML을 미리 만들어둘 수 있다.
빌드 시점:
블로그 글 100개 HTML 미리 생성
사용자 요청:
이미 만들어진 HTML 바로 응답
이 방식은 빠르고 서버 비용도 적다.
SEO가 필요 없다면 꼭 SSR일 필요는 없다
SSR은 SEO에 유리하다.
검색 엔진이나 공유 미리보기 봇이 서버에서 완성된 HTML을 받을 수 있기 때문이다.
하지만 모든 페이지가 SEO를 필요로 하는 것은 아니다.
예를 들어 로그인 후에만 볼 수 있는 관리자 페이지, 개인 대시보드, 내부 도구는
검색 엔진에 노출될 필요가 거의 없다.
이런 경우 SSR을 무조건 쓸 필요는 없다.
CSR이나 Pre-rendering, 또는 혼합 방식을 선택할 수 있다.
핵심은:
SSR이 항상 정답은 아니다.
페이지 성격에 따라 렌더링 방식을 선택해야 한다.
21. SSR은 무조건 빠른가?
아니다.
SSR은 첫 화면을 빠르게 보여줄 수 있는 장점이 있지만,
항상 CSR보다 빠른 것은 아니다.
특히 서버에서 여러 데이터를 순차적으로 기다려야 하면 오히려 느려질 수 있다.
예시: 상품 상세 페이지
상품 상세 페이지를 SSR로 만든다고 해보자.
필요한 데이터가 여러 서버에 나뉘어 있다.
상품 서버
리뷰 서버
할인 서버
배송 서버
재고 서버
이때 화면을 그리기 위해 모든 데이터가 필요하다고 가정하자.
나쁜 구조는 이렇다.
1. 상품 서버 호출
2. 상품 데이터를 받아야 리뷰 서버 호출 가능
3. 리뷰 데이터를 받아야 할인 서버 호출 가능
4. 할인 데이터를 받아야 배송 서버 호출 가능
5. 배송 데이터를 받아야 재고 서버 호출 가능
6. 전부 끝나야 HTML 생성
이렇게 순차적으로 기다리면 SSR 응답이 매우 느려진다.
사용자는 서버가 모든 데이터를 모을 때까지 아무 화면도 못 볼 수 있다.
이러면 SSR을 속도 때문에 도입했는데,
오히려 CSR보다 느린 상황이 생길 수 있다.
그럼 어떻게 해야 할까?
화면을 그릴 때 정말 필요한 데이터와 나중에 불러와도 되는 데이터를 구분해야 한다.
예를 들어 상품 상세 페이지에서 가장 먼저 필요한 것은 다음일 수 있다.
- 상품명
- 대표 이미지
- 가격
- 구매 버튼
- 기본 옵션
반면 다음 데이터는 나중에 불러와도 될 수 있다.
- 리뷰 목록
- 추천 상품
- 배송 상세 안내
- 실시간 재고
- 할인 쿠폰 목록
그럼 SSR에서는 핵심 데이터만 포함해서 HTML을 빨리 내려준다.
SSR:
상품명, 대표 이미지, 가격, 구매 버튼
나머지는 브라우저에서 useEffect나 React Query 같은 도구로 나중에 불러온다.
CSR:
리뷰, 추천 상품, 배송 정보, 쿠폰 정보
이렇게 하면 첫 화면은 빠르게 보여주고,
부가 데이터는 스켈레톤 UI와 함께 점진적으로 채울 수 있다.
22. useEffect는 제외하고 와꾸만 먼저 그린다는 말
강의에서 “useEffect는 제외하고, 나머지 와꾸만 먼저 그려주자”는 표현이 있었다.
이 말은 SSR에서 중요한 감각이다.
React 컴포넌트 안에 이런 코드가 있다고 해보자.
function ProductPage() { const [reviews, setReviews] = useState([]); useEffect(() => { fetch("/api/reviews") .then((res) => res.json()) .then(setReviews); }, []); return ( <main> <h1>상품명</h1> <ProductImage /> <Price /> <ReviewList reviews={reviews} /> </main> ); }
useEffect는 브라우저에서 렌더링이 끝난 뒤 실행된다.
서버에서는 실행되지 않는다.
그래서 SSR 시점에는 useEffect로 가져오는 데이터는 아직 없다.
서버는 일단 기본 HTML 구조를 만들어 내려준다.
상품명
상품 이미지
가격
리뷰 영역의 기본 구조
그 다음 브라우저에서 JS가 실행되고 Hydration이 끝난 뒤,
useEffect가 실행되어 리뷰 데이터를 가져온다.
브라우저에서 리뷰 API 호출
→ 리뷰 데이터 받음
→ 리뷰 영역 업데이트
즉 이 말은:
서버에서는 첫 화면에 꼭 필요한 구조와 데이터만 먼저 그리고,
브라우저에서 처리해도 되는 데이터는 useEffect로 나중에 가져오자.
라는 뜻이다.
23. 쿠팡 같은 서비스는 모든 상품 HTML을 미리 만들어두나?
쿠팡처럼 상품이 엄청 많은 서비스는 고민이 생긴다.
상품 페이지가 수백만 개라면 모든 HTML을 미리 만들어두는 것은 부담스럽다.
예를 들어 상품이 1,000만 개라면:
/product/1.html
/product/2.html
/product/3.html
...
/product/10000000.html
이걸 전부 미리 만들어두는 건 현실적으로 부담이 크다.
그래서 실제 서비스는 보통 여러 방식을 섞는다.
1. 인기 상품만 미리 생성
트래픽이 많은 상품 페이지만 미리 만들어둘 수 있다.
인기 상품 1만 개 → pre-rendering
나머지 상품 → 요청 시 SSR 또는 CSR
2. 요청이 들어올 때 생성하고 캐싱
처음 요청이 들어오면 서버가 HTML을 만들고,
그 결과를 캐시에 저장할 수 있다.
첫 요청:
서버가 HTML 생성 → 응답 → 캐시 저장
다음 요청:
캐시된 HTML 바로 응답
이런 방식은 모든 페이지를 미리 만들 필요가 없고,
사용자가 실제로 보는 페이지만 생성할 수 있다.
3. 핵심 정보만 SSR, 나머지는 클라이언트에서 로딩
상품명, 가격, 대표 이미지처럼 중요한 정보만 SSR하고,
리뷰나 추천 상품은 클라이언트에서 나중에 불러온다.
이 방식은 현실적으로 많이 사용된다.
SSR:
상품 기본 정보
CSR:
리뷰, 추천 상품, 쿠폰, 배송 상세
24. 렌더링 방식 비교
| 방식 | HTML을 누가 만드나 | 장점 | 단점 | 적합한 경우 |
|---|---|---|---|---|
| 정적 HTML | 미리 만들어둠 | 단순하고 빠름 | 데이터 변화 반영 어려움 | 회사 소개, 간단 문서 |
| SST | 서버 템플릿 | DB 데이터 반영 가능 | 페이지 이동마다 새 HTML | 전통적인 게시판, JSP/PHP |
| CSR SPA | 브라우저 JS | 이동 부드러움, 상태 유지 쉬움 | 첫 화면 느릴 수 있음, SEO 약함 | 관리자 페이지, 앱형 서비스 |
| SSR | 서버 | 첫 화면 빠름, SEO 유리 | 서버 비용, 데이터 의존성 크면 느림 | 검색 노출 중요한 페이지 |
| Pre-rendering | 빌드 시점 | 매우 빠름, 비용 적음 | 자주 바뀌는 데이터에 약함 | 블로그, 문서, 랜딩 페이지 |
| SSR + SPA | 첫 진입 서버, 이후 브라우저 | 초기 속도와 앱 경험 모두 확보 | 구조 복잡 | 현대 웹앱, 커머스, 포털 |
25. 프론트엔드 개발자가 렌더링 방식을 판단하는 기준
프론트엔드 개발자는 단순히 React 코드를 짜는 사람이 아니다.
어떤 페이지를 어떤 방식으로 렌더링해야 하는지 판단해야 한다.
다음 질문을 해볼 수 있다.
1. SEO가 중요한가?
검색엔진 노출이 중요하다면 SSR이나 Pre-rendering이 유리하다.
예를 들어:
- 상품 상세 페이지
- 블로그 글
- 뉴스 기사
- 채용 공고
- 랜딩 페이지
이런 페이지는 검색 결과에 잘 노출되어야 하므로 HTML에 의미 있는 콘텐츠가 포함되는 것이 좋다.
반면 로그인 후 대시보드나 관리자 페이지는 SEO가 거의 필요 없다.
이런 페이지는 CSR도 충분할 수 있다.
2. 첫 화면에 꼭 필요한 데이터는 무엇인가?
모든 데이터를 첫 화면 전에 다 가져오려고 하면 느려진다.
그래서 중요한 질문은 이것이다.
사용자가 이 페이지를 이해하기 위해 가장 먼저 봐야 하는 데이터는 무엇인가?
예를 들어 상품 상세 페이지라면:
꼭 필요:
상품명, 이미지, 가격, 구매 버튼
나중에 가능:
리뷰, 추천 상품, 배송 상세, 쿠폰
꼭 필요한 데이터만 먼저 렌더링하고,
나머지는 스켈레톤 UI와 함께 나중에 불러오는 것이 좋다.
3. 데이터가 얼마나 자주 바뀌는가?
자주 바뀌지 않는 데이터라면 Pre-rendering이나 캐싱이 좋다.
예를 들어:
- 회사 소개
- FAQ
- 블로그 글
- 약관
- 정적 문서
이런 페이지는 매 요청마다 SSR할 필요가 없다.
반대로 실시간 주식 가격, 채팅, 알림, 재고 같은 데이터는
정적 HTML로 해결하기 어렵다.
4. 사용자 상호작용이 많은가?
상호작용이 많고 앱처럼 동작해야 하는 화면은 SPA 방식이 잘 맞는다.
예를 들어:
- 관리자 대시보드
- 드래그 앤 드롭 편집기
- 채팅
- 필터가 많은 검색 페이지
- 복잡한 폼
- 지도 기반 서비스
이런 화면은 클라이언트 상태 관리가 중요하기 때문에 CSR/SPA 구조가 유리할 수 있다.
5. 서버 비용을 감당할 수 있는가?
SSR은 요청마다 서버에서 렌더링 비용이 발생할 수 있다.
트래픽이 많으면 서버 비용이 커진다.
그래서 무조건 SSR을 선택하는 것이 아니라,
페이지별로 다음을 나눠야 한다.
정적으로 만들어도 되는 페이지
요청마다 서버에서 만들어야 하는 페이지
브라우저에서 그려도 되는 페이지
26. 전체 흐름 한 번에 정리
웹은 다음 흐름으로 발전해왔다고 이해하면 좋다.
1. 정적 HTML
- HTML 파일을 미리 만들어두고 요청하면 그대로 내려줌
- 데이터가 바뀌는 서비스에는 한계
2. SST
- 서버가 DB 데이터를 HTML 템플릿에 넣어서 응답
- JSP, PHP, ASP 같은 방식
- 요청마다 최신 데이터 반영 가능
3. MPA
- 여러 페이지로 구성
- 이동할 때마다 서버에서 새 HTML을 받음
- 상태 유지가 불편함
4. AJAX
- 페이지 전체 새로고침 없이 일부 데이터만 갱신 가능
- 하지만 메뉴 이동은 여전히 MPA인 경우가 많음
5. SPA
- HTML 하나를 받고 JS가 화면을 렌더링
- 페이지 이동은 URL만 바꾸고 컴포넌트 교체
- 상태 유지와 부드러운 이동이 장점
- 첫 화면 렌더링이 느릴 수 있음
6. SSR
- 서버에서 React 결과를 HTML로 만들어 보냄
- 첫 화면과 SEO에 유리
- 서버 비용과 데이터 의존성 문제가 있음
7. SSR + SPA
- 첫 진입은 SSR
- 이후 이동은 JSON만 받아 SPA처럼 화면 변경
- 현대 웹앱에서 많이 쓰는 혼합 방식
27. 마지막으로: 프론트엔드 개발자의 진짜 역할
프론트엔드 개발자는 단순히 화면을 그리는 사람이 아니다.
사용자가 서비스를 어떻게 느끼는지를 책임지는 사람이다.
같은 API 응답 속도라도 프론트엔드 구현에 따라 사용자의 느낌은 완전히 달라진다.
빈 화면 2초
→ 느리고 멈춘 것 같음
스켈레톤 2초
→ 로딩 중임을 이해함
옵티미스틱 UI
→ 즉시 반응하는 것처럼 느껴짐
SSR
→ 첫 화면이 빠르게 보임
SPA 라우팅
→ 페이지 이동이 부드러움
결국 프론트엔드 개발자는 다음을 계속 판단해야 한다.
- 이 데이터는 서버에서 먼저 그릴까?
- 브라우저에서 나중에 가져와도 될까?
- 스켈레톤이 필요한가?
- 옵티미스틱 UI를 적용해도 안전한가?
- SEO가 중요한 페이지인가?
- SSR을 쓰면 정말 빨라지는가?
- 오히려 서버 데이터 의존성 때문에 느려지지는 않는가?
- 사용자가 지금 무엇을 기다리고 있는지 이해할 수 있는가?
이런 판단이 쌓여서 좋은 사용자 경험이 된다.
그래서 프론트엔드 개발자는 단순히 UI를 구현하는 사람이 아니라,
네트워크, 렌더링, 상태 관리, 사용자 체감 속도를 모두 고려해 제품 경험을 설계하는 개발자라고 볼 수 있다.