Don't be afraid of challenges
[nextjs] Rendering 본문
기본적으로 app 폴더 하위의 모든 컴포넌트는 서버 컴포넌트
→ 개발을 하다보면 client-side rendering을 위한 기술이 필요할 때도 있는데 "use client" 코드를 컴포넌트 최상단에 붙여넣기 하면 됨
언제 쓰냐 ?
User와의 상호작용이 있는경우 CC, 그 외는 SC (그니까 default가 SC고 필요한 경우에 CC를 쓰자)
fetch를 통해 여러가지 렌더링 패턴에 대해 이해 할 수 있다. 컴포넌트의 변화는 사실 fetch한 데이터를 기준으로 변경되기 때문에 fetch 데이터의 변경은 컴포넌트 렌더링 방식을 규정한다고 볼수 있다
1. SSG (Static Site Generation)
fetch한 데이터는 영원히 변치 않고, 계속 갱신할 필요가 없다
SSG는 빌드타임(build-time) 때만 컴포넌트를 생성하고, 이후는 변하지 않는 페이지로 가정하여 static 컴포넌트를 제공하는 것을 의미한다. 그리고 next.js는 아무것도 하지 않으면 기본적으로 SSG로 동작한다
// src>app>rendering/page.tsx
import SSG from "@/components/rendering/SSG";
import React from "react";
const RenderingTestPage = () => {
return (
<div>
<h1>4가지 렌더링 방식을 테스트합니다.</h1>
<SSG />
</div>
);
};
export default RenderingTestPage;
import Image from "next/image";
import React from "react";
import type { RandomUser } from "@/types";
// SSG TEST : 아무것도 하지 않으면 SSG!
const SSG = async () => {
// (1) 첫 번째 방법 : 아무 옵션도 부여 x
const response = await fetch(`https://randomuser.me/api`, {
cache: "force-cache", // (2) 두 번째 방법 : cache에 force-cache 옵션 부여
});
const { results } = await response.json();
const user: RandomUser = results[0];
return (
<div className="mt-8">
<div className="border p-4 my-4">
<div className="flex gap-8">
{/* 유저 기본정보 */}
<div>
<Image
src={user.picture.large}
alt={user.name.first}
width={200}
height={200}
/>
<h2 className="text-xl font-bold">
{user.name.title}. {user.name.first} {user.name.last}
</h2>
<p className="text-gray-600">{user.email}</p>
<div className="mt-4">
<p>
<span className="font-bold">전화번호 : </span>
{user.phone}
</p>
<p>
<span className="font-bold">휴대전화번호 : </span>
{user.cell}
</p>
<p>
<span className="font-bold">사는 곳 : </span>
{user.location.city}, {user.location.country}
</p>
<p>
<span className="font-bold">등록일자 : </span>
{new Date(user.registered.date).toLocaleDateString()}
</p>
<p>
<span className="font-bold">생년월일 : </span>
{new Date(user.dob.date).toLocaleDateString()}
</p>
</div>
</div>
{/* 지도영역 */}
<iframe
src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
height="450"
width="600"
></iframe>
</div>
</div>
</div>
);
};
export default SSG;
2. ISR (Incremental Site Regeneration)
fetch한 데이터는 가끔 변한다. 일정한 주기마다 가끔씩만 컴포넌트를 갱신한다
ISR은 빌드타임(build-time) 때 컴포넌트를 초기 생성하고, 이후는 일정 주기마다 변화를 적용하여 컴포넌트를 제공하는 것을 말한다
// src>app>rendering.tsx
import ISR from "@/components/rendering/ISR";
import React from "react";
const RenderingTestPage = () => {
return (
<div>
<h1>4가지 렌더링 방식을 테스트합니다.</h1>
<ISR />
</div>
);
};
export default RenderingTestPage;
(1) fetch에 옵션 주기
import Image from "next/image";
import React from "react";
import type { RandomUser } from "@/types";
const ISR = async () => {
const response = await fetch(`https://randomuser.me/api`, {
next: {
revalidate: 5,
},
});
const { results } = await response.json();
const user: RandomUser = results[0];
return (
<div className="mt-8">
<div className="border p-4 my-4">
<div className="flex gap-8">
{/* 유저 기본정보 */}
<div>
<Image
src={user.picture.large}
alt={user.name.first}
width={200}
height={200}
/>
<h2 className="text-xl font-bold">
{user.name.title}. {user.name.first} {user.name.last}
</h2>
<p className="text-gray-600">{user.email}</p>
<div className="mt-4">
<p>
<span className="font-bold">전화번호 : </span>
{user.phone}
</p>
<p>
<span className="font-bold">휴대전화번호 : </span>
{user.cell}
</p>
<p>
<span className="font-bold">사는 곳 : </span>
{user.location.city}, {user.location.country}
</p>
<p>
<span className="font-bold">등록일자 : </span>
{new Date(user.registered.date).toLocaleDateString()}
</p>
<p>
<span className="font-bold">생년월일 : </span>
{new Date(user.dob.date).toLocaleDateString()}
</p>
</div>
</div>
{/* 지도영역 */}
<iframe
src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
height="450"
width="600"
></iframe>
</div>
</div>
</div>
);
};
export default ISR;
(2) page.tsx 컴포넌트에 revalidate 추가하기
// src>app>rendering>page.tsx
import ISR from "@/components/rendering/ISR";
import React from "react";
export const revalidate = 5;
const RenderingTestPage = () => {
return (
<div>
<h1>4가지 렌더링 방식을 테스트합니다.</h1>
<ISR />
</div>
);
};
export default RenderingTestPage;
import Image from "next/image";
import React from "react";
import type { RandomUser } from "@/types";
const ISR = async () => {
const response = await fetch(`https://randomuser.me/api`);
const { results } = await response.json();
const user: RandomUser = results[0];
return (
<div className="mt-8">
<div className="border p-4 my-4">
<div className="flex gap-8">
{/* 유저 기본정보 */}
<div>
<Image
src={user.picture.large}
alt={user.name.first}
width={200}
height={200}
/>
<h2 className="text-xl font-bold">
{user.name.title}. {user.name.first} {user.name.last}
</h2>
<p className="text-gray-600">{user.email}</p>
<div className="mt-4">
<p>
<span className="font-bold">전화번호 : </span>
{user.phone}
</p>
<p>
<span className="font-bold">휴대전화번호 : </span>
{user.cell}
</p>
<p>
<span className="font-bold">사는 곳 : </span>
{user.location.city}, {user.location.country}
</p>
<p>
<span className="font-bold">등록일자 : </span>
{new Date(user.registered.date).toLocaleDateString()}
</p>
<p>
<span className="font-bold">생년월일 : </span>
{new Date(user.dob.date).toLocaleDateString()}
</p>
</div>
</div>
{/* 지도영역 */}
<iframe
src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
height="450"
width="600"
></iframe>
</div>
</div>
</div>
);
};
export default ISR;
3. SSR(Server Side Rendering)
fetch한 데이터는 실시간으로 계속 바뀐다. 컴포넌트 요청이 있을 때 마다 데이터를 갱신해서 최신 데이터만 제공해야 한다
SSR는 빌드타임(build-time) 때 컴포넌트를 초기 생성하고, 이후 컴포넌트 요청이 있을 때 마다 변화를 적용하여 가장 최신의 데이터를 user에게 제공한다
(fetch 시, 매번 갱신해라, 캐시된 데이터는 취급하지 않아 ! 옵션 준다면 SSR)
// src>app>rendering>page.tsx
import SSR from "@/components/rendering/SSR";
import React from "react";
const RenderingTestPage = () => {
return (
<div>
<h1>4가지 렌더링 방식을 테스트합니다.</h1>
<SSR />
</div>
);
};
export default RenderingTestPage;
// src>components>rendering>SSR.tsx
import Image from "next/image";
import React from "react";
import type { RandomUser } from "@/types";
const SSR = async () => {
const response = await fetch(`https://randomuser.me/api`, {
cache: "no-cache",
});
const { results } = await response.json();
const user: RandomUser = results[0];
return (
<div className="mt-8">
<div className="border p-4 my-4">
<div className="flex gap-8">
{/* 유저 기본정보 */}
<div>
<Image
src={user.picture.large}
alt={user.name.first}
width={200}
height={200}
/>
<h2 className="text-xl font-bold">
{user.name.title}. {user.name.first} {user.name.last}
</h2>
<p className="text-gray-600">{user.email}</p>
<div className="mt-4">
<p>
<span className="font-bold">전화번호 : </span>
{user.phone}
</p>
<p>
<span className="font-bold">휴대전화번호 : </span>
{user.cell}
</p>
<p>
<span className="font-bold">사는 곳 : </span>
{user.location.city}, {user.location.country}
</p>
<p>
<span className="font-bold">등록일자 : </span>
{new Date(user.registered.date).toLocaleDateString()}
</p>
<p>
<span className="font-bold">생년월일 : </span>
{new Date(user.dob.date).toLocaleDateString()}
</p>
</div>
</div>
{/* 지도영역 */}
<iframe
src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
height="450"
width="600"
></iframe>
</div>
</div>
</div>
);
};
export default SSR;
4. CSR(Client Side Rendering)
fetch한 데이터는 실시간으로 계속 바뀐다. 컴포넌트 요청이 있을 때 마다 데이터를 갱신해서 최신 데이터만 제공해야 한다
CSR은 빌드타임에 컴포넌트를 초기 생성하진 않는다. javascript로 이루어진 리액트 파일을 다운로드 받고 그제서야 화면이 그려진다
// src>app>rendering.tsx
import CSR from "@/components/rendering/CSR";
import React from "react";
const RenderingTestPage = () => {
return (
<div>
<h1>4가지 렌더링 방식을 테스트합니다.</h1>
<CSR />
</div>
);
};
export default RenderingTestPage;
"use client";
import Image from "next/image";
import React, { useEffect, useState } from "react";
import type { RandomUser } from "@/types";
const SSG = () => {
const [user, setUser] = useState<RandomUser | null>(null);
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(`https://randomuser.me/api`);
const { results } = await response.json();
setUser(results[0]);
};
fetchUser();
}, []);
if (!user) {
return <div>로딩중...</div>;
}
return (
<div className="mt-8">
<div className="border p-4 my-4">
<div className="flex gap-8">
{/* 유저 기본정보 */}
<div>
<Image
src={user.picture.large}
alt={user.name.first}
width={200}
height={200}
/>
<h2 className="text-xl font-bold">
{user.name.title}. {user.name.first} {user.name.last}
</h2>
<p className="text-gray-600">{user.email}</p>
<div className="mt-4">
<p>
<span className="font-bold">전화번호 : </span>
{user.phone}
</p>
<p>
<span className="font-bold">휴대전화번호 : </span>
{user.cell}
</p>
<p>
<span className="font-bold">사는 곳 : </span>
{user.location.city}, {user.location.country}
</p>
<p>
<span className="font-bold">등록일자 : </span>
{new Date(user.registered.date).toLocaleDateString()}
</p>
<p>
<span className="font-bold">생년월일 : </span>
{new Date(user.dob.date).toLocaleDateString()}
</p>
</div>
</div>
{/* 지도영역 */}
<iframe
src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
height="450"
width="600"
></iframe>
</div>
</div>
</div>
);
};
export default SSG;'nextjs' 카테고리의 다른 글
| [nextjs] 페이지네이션 (1) | 2024.07.11 |
|---|---|
| [nextjs] contextAPI로 alert, modal창 만들기 (1) | 2024.07.10 |
| [nextjs] 좋아요 기능 구현 + 낙관적 업데이트 (0) | 2024.07.10 |
| [nextjs] routing (1) | 2024.07.03 |
| [nextjs] 기초 정리 (2) | 2024.07.03 |