hmk run dev
Next.js 상황에 맞는 렌더링 방식 선택 가이드 본문
Next.js는 다양한 렌더링 방식을 제공합니다. 정적 생성(SSG), 서버 사이드 렌더링(SSR), 클라이언트 사이드 렌더링(CSR), 그리고 점진적 정적 생성(ISR)까지 다양한 전략이 가능하죠. 버전이 14에 도달하면서 App Router가 정식화되었고, fetch() 내장, cache, revalidate, searchParams, server/client component 분리 등 보다 유연한 방식으로 구성할 수 있게 되었습니다.
하지만 기능이 다양해진 만큼 "어떤 상황에서 어떤 렌더링 방식을 선택해야 하는지"는 더욱 고민되는 지점입니다. 특히 실무에서 많이 마주치는 SKU 기반 조합 페이지나, 필터형 검색 결과 페이지 등에서는 의사결정이 쉽지 않습니다.
이번 글에서는 실제 사례를 중심으로, 어떤 렌더링 방식을 선택해야 할지를 정리해 보겠습니다.
렌더링 방식 정리
방식 | 설명 | 대표 사용 상황 |
정적 생성 (SSG) | 빌드 시 HTML 생성 | 마케팅 랜딩, 공지사항 |
서버 사이드 렌더링 (SSR) | 요청 시 서버에서 HTML 생성 | 로그인 필요, 맞춤형 대시보드 |
클라이언트 렌더링 (CSR) | 브라우저에서 fetch 후 렌더 | 인터랙션 위주 페이지, 설정/필터 |
ISR (revalidate) | 일정 주기로 정적 페이지 재생성 | 실시간성이 낮은 상품 페이지 |
SKU/옵션 조합 페이지에서의 고민
실무에서는 핸드폰, 노트북 등 상품을 선택할 때 "기기 + 요금제 + 색상" 같은 옵션 조합에 따라 화면이 바뀌는 구조를 자주 마주칩니다. 이 경우 페이지 설계 방향은 다음 두 가지 중 하나로 갈 수 있습니다.
- 다이나믹 라우트 기반 SSR (/sku/[id])
- searchParams 기반 SSR 또는 CSR (/sku?plan=lte&color=black)
다이나믹 라우트 SSR vs searchParams 기반 구조 비교
항목 | 다이나믹 라우트 SSR | searchParams 기반 |
URL 형태 | /sku/iphone15 | /sku?device=iphone15&plan=lte |
서버 fetch 방식 | params로 식별 | searchParams 파싱 |
SEO | 매우 우수 (경로별 인덱싱) | 상대적으로 제한적 |
조합 수 많을 때 | URL 폭발 가능성 있음 | 유연하게 대응 가능 |
UX (옵션 변경 시) | 페이지 이동 필요 | URL만 바꾸고 데이터만 교체 가능 |
캐시 최적화 | ISR 가능 | 캐시 활용은 fetch 조합 기반으로 (reavalidate) |
결론적으로, 옵션 조합이 매우 다양해질 가능성이 높고 유저가 선택을 반복적으로 할 수 있는 구조라면 searchParams 기반으로 렌더링 구조를 잡는 것이 더 효율적일 수 있습니다.
다른 방법: SSR + CSR 하이브리드 방식
Next.js 14의 App Router에서는 서버 컴포넌트에서 SSR을 처리하고, 클라이언트 컴포넌트에서는 React Query 같은 라이브러리를 활용해 CSR 렌더링을 적용하는 하이브리드 방식이 가능합니다.
구조 요약
- 첫 진입 시에는 SSR로 searchParams 기반 데이터를 서버에서 가져옴
- 이후 사용자가 요금제, 컬러 등을 변경하면 CSR(fetch) 방식으로 화면만 바뀜
- URL은 항상 현재 조합을 반영하고 있어 공유 가능함
// app/sku/page.tsx
import { fetchSku } from '@/lib/api'
import SkuClient from './SkuClient'
export default async function Page({ searchParams }) {
const data = await fetchSku(searchParams)
return <SkuClient initialData={data} initialParams={searchParams} />
}
// app/sku/SkuClient.tsx
'use client'
import { useQuery } from '@tanstack/react-query'
import { useSearchParams, useRouter } from 'next/navigation'
export default function SkuClient({ initialData, initialParams }) {
const router = useRouter()
const params = useSearchParams()
const queryKey = ['sku', params.toString()]
const { data } = useQuery({
queryKey,
queryFn: () => fetch(`/api/sku?${params}`).then(res => res.json()),
initialData,
})
const handleChange = (newParams) => {
router.replace(`?${newParams.toString()}`)
}
return (
<>
<OptionSelector onChange={handleChange} />
<SkuViewer data={data} />
</>
)
}
CSR 하이브리드 구조에서 클라이언트 컴포넌트가 메인 콘텐츠를 담당하면,
그 부분은 HTML로 SSR 되지 않아 SEO가 약해지는 것 아닌가?
핵심 정리 먼저
- ✅ 맞습니다: 클라이언트 컴포넌트 내부에서만 콘텐츠를 fetch해 렌더링하면, HTML에 해당 콘텐츠가 포함되지 않기 때문에 검색엔진(특히 Googlebot)이 제대로 인덱싱하지 못할 수 있습니다.
- ✅ 맞습니다: 반대로 서버 컴포넌트만으로 구성하면 HTML에 실제 콘텐츠가 포함되므로 SEO에 훨씬 유리합니다.
- ✅ 맞습니다: 클릭 가능한 요소(Link)만 있다면 그것은 클라이언트 컴포넌트일 필요는 없습니다. <Link>는 서버 컴포넌트에서도 사용할 수 있습니다.
CSR 하이브리드 구조의 SEO 한계
// 예: 클라이언트 컴포넌트 내부에서만 데이터 fetch
'use client'
const Page = () => {
const [data, setData] = useState(null)
useEffect(() => {
fetch('/api/sku?plan=lte&color=black').then(res => res.json()).then(setData)
}, [])
return <>{data?.title}</>
}
이 경우:
- 초기 HTML에는 data가 없음
- 검색엔진이 이 페이지를 방문해도 콘텐츠를 못 보고 지나침
- CSR로만 구성된 콘텐츠는 SEO 측면에서는 거의 의미 없음 (특히 meta tag와 og tag도 CSR이면 의미 없음)
서버 컴포넌트만으로도 인터랙션 가능한가?
Next.js App Router에서 <Link> 같은 인터랙션 컴포넌트는 서버 컴포넌트에서도 사용할 수 있습니다.
// 서버 컴포넌트에서도 사용 가능
<Link href="/sku?plan=lte&color=black">이 요금제 보기</Link>
<Link> 자체는 클라이언트에서 hydrate되지만, HTML은 서버에서 내려오기 때문에 SEO 노출에도 전혀 문제 없습니다.
그럼 더 나은 방법은?
당신이 원하시는 것이:
- SEO 잘 되면서
- 인터랙션 UX도 자연스럽고
- 서버 부하도 최소화하고 싶다면
추천 구조: 서버 컴포넌트 SSR + 부분적 클라이언트 CSR
// /app/sku/page.tsx (서버 컴포넌트)
import { fetchSku } from '@/lib/api'
import { SkuList } from './SkuList'
export default async function Page({ searchParams }) {
const data = await fetchSku(searchParams) // SSR 시점에 데이터 fetch됨
return (
<>
<h1>{data.title}</h1> {/* HTML에 포함되어 인덱싱 가능 */}
<SkuList data={data.items} />
</>
)
}
// SkuList.tsx (클라이언트 컴포넌트 아님)
import Link from 'next/link'
export function SkuList({ data }) {
return (
<ul>
{data.map(item => (
<li key={item.id}>
<Link href={`?plan=${item.plan}&color=${item.color}`}>{item.name}</Link>
</li>
))}
</ul>
)
}
핵심 특징
- 메인 콘텐츠는 서버에서 fetch 되어 HTML에 포함 → SEO 매우 우수
- 클릭 가능한 요소는 서버 컴포넌트에서도 <Link>로 자연스럽게 처리
- 선택 이벤트 처리만 필요한 곳에 use client를 붙인 클라이언트 컴포넌트를 사용 (예: 옵션 선택 슬라이더 등)
언제 어떤 방식이 적합한가
상황 | 추천 방식 |
고정된 콘텐츠, 마케팅성 페이지 | SSG (정적 생성) |
로그인 필요, 세션 기반 페이지 | SSR (server component) |
실시간 옵션 선택이 많은 페이지 | CSR 또는 SSR+CSR 하이브리드 |
검색 필터, 리스트 결과 | searchParams 기반 CSR |
SEO 최적화 필요 + 경로별 인덱싱 | 다이나믹 라우트 SSR 또는 ISR |
마치며
Next.js 14는 SSR과 CSR, 그리고 정적 생성 방식들을 상황에 따라 혼합할 수 있는 매우 유연한 아키텍처를 제공합니다. 중요한 것은 성능 최적화나 개발 편의성보다 사용자 경험과 비즈니스 목표에 맞는 구조를 선택하는 것입니다.
특히 선택 조합이 많은 SKU 페이지처럼 복잡한 UX를 다루는 경우, searchParams 기반 SSR + CSR 구조가 성능과 UX, 유지보수성 모두에서 매우 이상적인 선택이 될 수 있습니다.
'Front-End' 카테고리의 다른 글
카카오 로그인 URL 길이 제한 문제와 해결방안 (0) | 2025.05.05 |
---|---|
Next.js에서 SEO 최적의 렌더링 전략 (0) | 2025.04.25 |
Next.js 서버사이드 keep alive (0) | 2025.04.22 |
레거시를 만들지 않기 위한 습관 (0) | 2024.12.15 |
yarn plugin으로 우아하게 로깅하기 (0) | 2024.10.20 |