"왜 이 프로젝트에 Next.js 쓰셨어요?" 하면 "SEO 때문에요"까지는 자신 있게 말하는데, "그게 정확히 왜 그런데요?"라고 한 마디만 더 파고들면 말문이 막혔다. 리액트가 SPA고 Next.js가 SSR인 건 아는데, 그 차이가 실제로 뭘 의미하는지는 두루뭉실했던 것 같다.
그래서 궁금했던 것들을 하나씩 파봤다. 질문했던 순서 그대로 정리해보았다!
Q. SSR이면 로딩이 아예 없는 게 아닐까? 왜 더 빠른 걸까?
로딩이 없어지는 게 아니었다. 누가 그 로딩 시간을 기다리느냐가 다른 것이었다.
- React (SPA): 브라우저가 거의 빈 HTML을 받는다 → JS를 다운로드한다 → JS가 실행되면서 화면을 그린다. 이 과정을 전부 사용자 브라우저 앞에서 한다.
- Next.js (SSR): 서버가 데이터를 fetch하고 HTML을 미리 다 조립해서 보낸다. 이 대기 시간이 사용자 눈에 안 보이는 서버 안에서 끝나 있다.
즉 SSR이 "빠르다"는 건 시간 자체가 줄어드는 게 아니라, 콘텐츠가 화면에 뜨기까지 걸리는 체감 시간이 짧아지는 것이었다.
Q. 로딩스피너가 보이는 것과 바로 뜨는 것, 정확히 무슨 차이일까?
SPA는 "빈 화면/스피너 → 콘텐츠 등장" 순서라 그 사이 사용자가 로딩 상태를 눈으로 보게 된다. SSR은 서버가 완성해서 보내주기 때문에 브라우저가 받자마자 콘텐츠가 바로 보인다. 사용자 입장에서는 "로딩 중" 상태 자체를 못 보는 셈이다.
Q. 크롤링 이슈라는 게 정확히 뭘까?
구글 크롤러가 페이지에 들어왔을 때를 생각해보면 이해가 쉽다.
- SSR → 완성된 HTML을 바로 받아서 콘텐츠를 다 읽어간다.
- SPA → 크롤러가 받는 건 빈 <div id="root"></div> 뿐이다. JS를 실행해야 콘텐츠가 채워지는데, 크롤러가 JS 실행을 기다려주는지는 상황마다 달라서 콘텐츠를 못 읽거나 늦게 읽는 경우가 생긴다.
이게 "SPA는 SEO에 불리하다"는 말의 실체였다.
Q. Next.js 할 때에는 로딩 처리 UI를 작성한 기억이 없는 것 같다.. 왜일까?
데이터 fetch가 서버 쪽 함수 안에서 일어나기 때문에, 그 함수가 끝나야 페이지 자체가 사용자한테 전달된다. 로딩 스피너를 보여줄 타이밍 자체가 없는 것이다 (페이지가 아직 도착하기 전이니까). 그래서 자연스럽게 로딩 UI 코드를 따로 짤 일이 없었던 것이다.
Q. Next.js는 프론트엔드와 백엔드가 둘 다 합쳐진 게 아닐까?
반은 맞고 반은 오해였다. Next.js 자체는 프론트엔드 프레임워크인데, app/api/... 폴더에 파일을 만들면 그게 하나의 백엔드 엔드포인트가 된다 (API Routes). 다만 이건 옵션이지 필수가 아니다. 복잡한 백엔드(대규모 DB 로직, 인증 시스템 등)는 여전히 별도 백엔드 서버를 두고, Next.js는 그 API를 호출해서 데이터만 받아오는 역할을 하는 경우가 많다.
Q. API Routes는 정확히 언제 쓰는 걸까?
브라우저에 노출되면 안 되는 로직이나 서버에서만 할 수 있는 작업이 있을 때 쓴다. 예를 들면 다음과 같다.
- 문의하기 폼 제출 → 이메일 발송 처리
- DB 직접 접근
- 외부 API 키를 프론트에 노출하지 않고 서버에서만 호출
완전 정적인 콘텐츠 페이지에는 필요 없다. (참고로 회사 홈페이지 만들 때 문의하기 폼에 이메일 전송 로직이 있었다면, 그게 바로 이 케이스였다.)
Q. 같은 걸 리액트로 만들었으면 로딩이 생겼을까?
콘텐츠가 완전 정적이라면 큰 차이가 없을 수도 있지만, 리액트는 기본적으로 브라우저에서 JS 실행 후 렌더링하는 구조라 첫 화면이 뜨기까지의 공백(빈 화면 → JS 로드 → 렌더링)이 여전히 생긴다. 결정적인 차이는 역시 SEO다. 크롤러가 처음 받는 HTML이 거의 비어있어서 콘텐츠를 못 읽어갈 가능성이 커진다.
Q. SEO가 필요한 부분에는 use client를 쓰면 안 되는 게 아닐까?
이 부분이 제일 크게 오해하고 있던 지점이었다. use client를 썼다고 해서 그 페이지가 SEO를 완전히 잃는 게 아니었다.
Next.js는 클라이언트 컴포넌트도 서버에서 최초 HTML을 한 번 렌더링해서 보내준다 (하이드레이션). use client는 "서버 렌더링을 안 한다"는 뜻이 아니라, **"이 컴포넌트는 브라우저 상호작용(state, 이벤트, hooks)이 필요하다"**는 표시일 뿐이다. 대부분의 경우 서버에서 초기 HTML은 만들어서 보내준다.
진짜 SEO에 안 좋은 경우는 페이지의 핵심 텍스트 자체가 useEffect로 마운트된 후에 fetch되는 구조일 때다.
Q. 그럼 use client를 안 넣어야 하는 데는 어디일까?
페이지 전체가 아니라 컴포넌트 단위로 넣는 것이었다.
ProductPage (서버 컴포넌트)
├── ProductTitle (서버) — SEO 대상 텍스트
├── ProductDescription (서버) — SEO 대상 텍스트
└── AddToCartButton (클라이언트, use client) — onClick 필요
텍스트나 콘텐츠를 다루는 컴포넌트는 서버 컴포넌트로 두고, 상호작용(버튼, 폼 입력 등)이 필요한 부분만 좁게 클라이언트 컴포넌트로 쪼개는 게 정석이다. 최상단에 걸면 자식 컴포넌트가 전부 클라이언트로 전파되긴 하지만, 콘텐츠가 SEO에 얼마나 중요한지에 따라 괜찮을 수도 있다.
Q. 로그인하는 서비스는 리액트가 좋다는 뜻일까?
이건 너무 단편적인 결론이었다. 정확한 기준은 로그인 여부가 아니라 **"이 화면을 검색엔진이 봐야 하는가"**다. 로그인 후 대시보드도 Next.js로 얼마든지 만든다 (넷플릭스, 노션 등이 그렇다). 그 페이지들은 그저 SSR의 이점(SEO)을 못 누릴 뿐이지, 손해 볼 것도 없다.
Q. 로그인 후에는 왜 SEO가 작동을 안 하는 걸까?
- 검색엔진 크롤러는 로그인을 못 한다 → 애초에 접근 자체가 불가능하다.
- 검색하는 사람도 "내 개인 대시보드"를 구글에서 찾아 들어오지 않는다 → 검색해서 발견하는 성격의 콘텐츠가 아니다.
Q. 서버 렌더링 인프라가 필요할 만큼 복잡하다는 기준은 뭘까?
- SEO가 조금이라도 필요한가
- 초기 로딩 속도가 이탈률에 직결되는가 (랜딩페이지, 이커머스처럼 로딩이 뜨면 그냥 나가버리는 경우)
- 로그인 뒤에만 보이는 내부 서비스인가 → 크롤러가 볼 일이 없다면 굳이 SSR 인프라가 필요 없다.
- 배포 환경이 단순해야 하는가
- 팀이나 프로젝트 규모가 작아서 빠르게 만들어야 하는가
Q. 배포는 왜 리액트가 더 간단할까?
- React: 빌드하면 정적 파일 뭉치(HTML/JS/CSS)가 나온다. 서버 프로세스 없이 아무 정적 호스팅에 올리면 끝이다.
- Next.js (SSR 모드): 요청마다 서버가 HTML을 조립해야 하기 때문에 Node.js 서버(또는 서버리스 함수)가 계속 떠있어야 한다.
Q. Next.js는 Netlify나 GitHub Pages로 배포할 수 없는 걸까?
- GitHub Pages: 순수 정적 파일만 서빙하는 곳이라 SSR 기능은 돌릴 수 없다. output: 'export'로 SSR을 포기하고 완전 정적으로 뽑아내야 배포가 가능하다.
- Netlify: 서버리스 함수 어댑터를 지원해서 SSR 기능을 살려서 배포할 수 있다.
- Vercel: Next.js를 만든 회사가 운영하는 곳이라 SSR을 가장 매끄럽게 지원한다.
결론
- SSR과 CSR의 차이: 로딩이 없어지는 게 아니라, 그 로딩을 누가(서버 혹은 브라우저) 먼저 감당하느냐의 차이였다.
- 선택 기준: "이 화면을 검색엔진이나 첫 방문자가 중요하게 보는가?" 하나로 정리된다.
- use client: 페이지 전체가 아니라 컴포넌트 단위로, 상호작용이 필요한 곳에만 최소로 적용하면 된다.
두루뭉실하게 "SEO 때문에요"로 넘어갔던 질문에, 이제는 한 단계 더 파고들어도 답할 수 있을 것 같다!!!
'Frontend' 카테고리의 다른 글
| 내가 보려고 정리해본 Next.js App Router를 볼 때 알아두면 좋은 기본 개념 (0) | 2026.09.02 |
|---|---|
| .cjs, .mjs 파일은 무엇일까? (0) | 2026.08.06 |
| 리액트 차트 라이브러리 react-chartjs-2 Line Chart 라인차트 구현하기, options prop 작성, 표 디자인 바꾸기 (0) | 2024.10.16 |
| 리액트 차트 라이브러리 react-chartjs-2 Line Chart 설치, 여러 줄 (데이터 여러개) 라인차트 구현하기, data prop 작성법 (4) | 2024.09.05 |
| setLoading 은 어디에 위치해야 할까? try 문 안에? 밖에!? (0) | 2024.06.05 |