오전 시간에는 Next.js 튜토리얼 실습을 진행해 페이지를 배포했다. 배포 링크는 아래에 있다!
https://next-js-dashboard-example-xi.vercel.app
클라이언트 측(Browser)에서 페이지 렌더링을 수행한다. SPA로 동작한다.
Next.js가 도입했다. 서버 실행 환경(런타임)을 도입해, 컨텐츠가 사용자에게 전달되는 방식과 CSR의 단점을 해소했다.
React18에서 컴포넌트를 통해 새로운 두 가지 기능을 제공하면서 기존 SSR 방식의 단점을 해소했다.
서버에서 HTML 코드를 부분적으로 스트리밍하는 방식이다.
SSR의 단점(Server에서는 렌더링 작업을 수행하는데 필요한 데이터들이 모두 다운로드 될 때까지, 렌더링을 시작할 수 없음)을 해소하였다.
<div>
<Header /> {/* 렌더링에 별도의 데이터가 필요없는 컴포넌트 */}
{/* <Dashboard /> 컴포넌트를 <Suspense />로 감싸서 지연 로딩시킴 */}
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
<Sidebar /> {/* 렌더링에 별도의 데이터가 필요없는 컴포넌트 */}
</div>

'use client'
import { useEffect, useState } from 'react'
import Dashboard from '@/components/1.csr/Dashboard'
import Sidebar from '@/components/1.csr/Sidebar';
import Header from '@/components/1.csr/Header';
export default function CSRPage() {
const [data, setData] = useState(null);
useEffect(() => {
console.log('useEffect 호출 됨');
fetch('http://localhost:4000/api/data?delay=2000')
.then(res => res.json())
.then(json => setData(json))
}, [])
return (
<div>
<section className="min-h-screen flex">
<aside className="w-64 bg-gray-100 border-r p-4">
<Sidebar />
</aside>
<div className="flex-1 flex flex-col">
<header className="h-16 bg-white border-b flex items-center justify-between px-6">
<Header />
</ header>
<main className="flex-1 p-6">
<h1 className="text-xl font-bold mb-4">Client-Side Rendering</h1>
{data === null ? (
<p>Loading...</p>
) : (
<Dashboard data={data} />
)}
</main>
</div>
</section>
</div>
)
}
Sidebar, Header를 먼저 로드한 후 useEffect 단에서 데이터를 받아온다. 데이터에 변화가 생겼으므로 다시 Sidebarr, Header가 렌더링된 후 대쉬보드가 렌더링된다.


import Dashboard from '@/components/2.ssr/Dashboard'
export const dynamic = 'force-dynamic'
export default async function SSRPage() {
const res = await fetch('http://localhost:4000/api/data?delay=2000', { cache: 'no-store' })
const data = await res.json()
return (
<div>
<h1 className="text-xl font-bold mb-4">Server-Side Rendering</h1>
<Dashboard data={data} />
</div>
)
}
별도의 표시가 없으면 use server이다. (next.js가 확장해 server에서도 fetch 함수를 사용 가능하게 했다.)
서버 측에서 데이터를 fetch 한 후 가져온다.
import { Suspense } from 'react'
import Dashboard from '@/components/2.ssr/Dashboard'
async function fetchData() {
const res = await fetch('http://localhost:4000/api/data?delay=2000', {
cache: 'no-store',
})
return res.json()
}
async function DashboardWithData() {
const data = await fetchData()
return <Dashboard data={data} />
}
export default function StreamingPage() {
return (
<div>
<h1 className="text-xl font-bold mb-4">Streaming SSR</h1>
{/* 렌더링을 지연시킬 부분만 Suspense로 감싸줌, <h1>Streaming SSR</h1>은 먼저 렌더링 */}
<Suspense fallback={<p>Loading...</p>}>
<DashboardWithData />
</Suspense>
</div>
)
}
Suspense로 감싸진 부분만 렌더링이 지연된다.
import LikeCounter from "./LikeCounter";
const Dashboard = ({ data }) => {
console.log('<Dashboard /> 렌더링 됨');
return (
<ul className="space-y-2">
{data.map((item, idx) => (
<li key={idx} className="p-2 bg-gray-100 rounded">
{item}
{/* 클라이언트 컴포넌트를 서버컴포넌트 Dashboard와 조합 */}
<LikeCounter item={item} />
</li>
))}
</ul>
)
}
export default Dashboard
서버 컴포넌트 안에 클라이언트 컴포넌트(LikeCounter)를 넣을 수 있다.

음료 목록은 SSR, 좋아요 버튼은 CSR이다.