icon안동민 개발노트

서버 사이드 렌더링 (SSR)


 Next.js의 서버 사이드 렌더링(SSR) 기능은 각 요청마다 서버에서 페이지를 동적으로 생성하여 클라이언트에 전송합니다.

 이 절에서는 SSR의 구현 방법, 장단점, 그리고 사용 사례에 대해 자세히 알아보겠습니다.

동적 SSR의 구현 방법

 Next.js 13 이상에서는 서버 컴포넌트를 사용하여 SSR을 쉽게 구현할 수 있습니다.

 기본적으로 모든 페이지 컴포넌트는 서버 컴포넌트로 동작하며, 요청 시 서버에서 렌더링됩니다.

 기본 SSR 구현 예제

app/dashboard/page.js
// app/dashboard/page.js
export default async function Dashboard() {
  const data = await fetchDashboardData()
  return <DashboardUI data={data} />
}

 이 컴포넌트는 매 요청마다 서버에서 실행되어 최신 데이터로 페이지를 생성합니다.

요청 시 데이터 페칭

 SSR에서는 각 요청마다 필요한 데이터를 페치할 수 있습니다.

 이는 항상 최신 데이터를 제공해야 하는 경우에 유용합니다.

app/profile/[id]/page.js
async function getUserProfile(id) {
  const res = await fetch(`https://api.example.com/users/${id}`, { cache: 'no-store' })
  if (!res.ok) throw new Error('Failed to fetch profile')
  return res.json()
}
 
export default async function Profile({ params }) {
  const profile = await getUserProfile(params.id)
  return <ProfileUI profile={profile} />
}

 여기서 { cache: 'no-store' } 옵션은 매 요청마다 새로운 데이터를 페치하도록 합니다.

SSR의 장단점

 장점

  1. 항상 최신 데이터 : 각 요청마다 최신 데이터로 페이지를 생성합니다.
  2. 개인화된 컨텐츠 : 사용자별 맞춤 컨텐츠를 제공할 수 있습니다.
  3. SEO 최적화 : 동적 컨텐츠도 검색 엔진에 잘 노출됩니다.
  4. 초기 로드 성능 : 클라이언트 사이드 렌더링에 비해 초기 로드가 빠릅니다.

 *단점

  1. 서버 부하 : 각 요청마다 서버 리소스를 사용합니다.
  2. TTFB(Time To First Byte) 증가 : 데이터 페칭으로 인해 응답 시간이 길어질 수 있습니다.
  3. 복잡성 : 서버와 클라이언트 로직을 관리해야 하므로 복잡도가 증가할 수 있습니다.

서버 컴포넌트 데이터 페칭과의 비교

 6장 1절에서 다룬 서버 컴포넌트에서의 데이터 페칭과 SSR은 밀접하게 연관되어 있지만, 몇 가지 차이점이 있습니다.

  1. 실행 시점 : 서버 컴포넌트는 빌드 시 또는 요청 시 실행될 수 있지만, SSR은 항상 요청 시 실행됩니다.
  2. 캐싱 : 서버 컴포넌트는 기본적으로 결과를 캐시하지만, SSR은 기본적으로 매 요청마다 새로운 결과를 생성합니다.
  3. 용도 : 서버 컴포넌트는 UI의 일부를 서버에서 렌더링하는 데 사용되고, SSR은 전체 페이지를 서버에서 렌더링합니다.

실습 : 사용자별 대시보드 페이지 SSR 구현

 다음 요구사항을 만족하는 개인화된 대시보드를 SSR로 구현해보세요.

  1. 사용자 ID를 쿼리 파라미터로 받아 해당 사용자의 정보를 표시
  2. 사용자의 최근 활동, 알림, 개인 설정 등을 포함
  3. 데이터는 매 요청마다 최신 정보를 페치
  4. 에러 처리와 로딩 상태 구현

 구현 예시

app/dashboard/page.js
import { Suspense } from 'react'
import { notFound } from 'next/navigation'
 
async function getUserData(userId) {
  const res = await fetch(`https://api.example.com/users/${userId}`, { cache: 'no-store' })
  if (!res.ok) return null
  return res.json()
}
 
async function getRecentActivity(userId) {
  const res = await fetch(`https://api.example.com/users/${userId}/activity`, { cache: 'no-store' })
  if (!res.ok) throw new Error('Failed to fetch recent activity')
  return res.json()
}
 
async function UserActivity({ userId }) {
  const activity = await getRecentActivity(userId)
  return (
    <ul>
      {activity.map(item => (
        <li key={item.id}>{item.description}</li>
      ))}
    </ul>
  )
}
 
export default async function Dashboard({ searchParams }) {
  const userId = searchParams.userId
  const userData = await getUserData(userId)
 
  if (!userData) {
    notFound()
  }
 
  return (
    <div>
      <h1>Welcome, {userData.name}!</h1>
      <h2>Your Recent Activity</h2>
      <Suspense fallback={<div>Loading activity...</div>}>
        <UserActivity userId={userId} />
      </Suspense>
      {/* 추가 대시보드 컴포넌트들 */}
    </div>
  )
}

 이 실습을 통해 SSR을 사용하여 동적이고 개인화된 페이지를 구현하는 방법을 경험할 수 있습니다.

 이는 사용자별 맞춤 컨텐츠를 제공해야 하는 대시보드, 소셜 미디어 피드, 이커머스 개인화 페이지 등에서 유용하게 사용될 수 있는 패턴입니다.