CSR의 문제점
SSR의 재등장
그러나 SSR의 문제점!
🗣️ 웹이 제대로 반응하지 않는다는 것은 무슨 의미인가요?
위의 질문에 대해 제대로 대답하기 위해서는 TTV, TTI라는 개념을 먼저 파악해야 한다.
TTV === TTI
사용자가 사이트 접속 시, 서버는 이미 만들어진 HTML 문서를 클라이언트에게 넘겨준다.
이 때문에 사용자는 화면을 바로 볼 수 있다.
하지만 아직 JS 파일은 받지 못한 상태이므로, 바로 상호작용이 불가능하다.
SSR에서는 TTV !== TTI
(시간차가 존재한다.)
문제점
📢 이를 해결하기 위해 등장한 SSG!
👩🏫 react-query를 생각해봅시다.
정적 페이지 제공 방식
이라고 표현한다.next build
명령어 입력 시 HTML 파일이 생성되고, CDN을 통해 캐싱된다.분류 | Meaning | 방식 |
---|---|---|
CSR | Client-Side-Rendering | 화면을 클라이언트단에서 바꾸는 것 |
SSR | Server-Side-Rendering | 화면을 서버단에서 전송해주는 것 |
SSG | Server-Side-Generation | 화면을 서버에서 미리 만들어 전송해주는 것 |
🧐 CSR, 언제 써야 할까?
🧐 SSR, 언제 써야 할까?
🧐 SSG, 언제 써야 할까?
Next.js에서는 성능 상의 차이로 인해 SSG 방식을 권장한다.
하지만 하나만 사용해야 하는 것은 아니고, 페이지마다 선택이 가능하다.
getServerSideProps( )
function Page({ data }) {
// 데이터를 렌더링하는 코드~
}
// 모든 요청에서 호출된다.
export async function getServerSideProps() {
// 외부 API에서 전송되는 데이터를 fetch
const res = await fetch(`https://.../data`);
const data = await res.json();
// Props를 사용해 페이지로 데이터를 전달한다.
return { props: { data } };
}
export default Page;
next/link
, next/router
를 통해 페이지 요청 시 Next.js가 서버에 API 요청을 보내고, 서버가 이 함수를 실행시킨다.⚠️ 이것은 주의하자!
function About() {
return <div>About Me :D</div>;
}
export default About;
getStaticProps( )
🧐 조금 더 구체적인 상황을 가정해보자.
function Blog({ posts }) {
// 게시글을 작성하는 코드~
}
// 이 함수는 빌드 시점에 호출된다.
export async function getStaticProps() {
// 게시글을 가져오기 위한 외부 API 엔드 포인트를 호출한다.
const res = await fetch("https://.../posts");
const posts = await res.json();
// 외부 API를 통해 가져온 데이터를 { props: { posts } }로 반환함으로써,
// 블로그 컴포넌트는 빌드 시점에 prop으로 'posts'를 받게 된다.
return {
props: {
posts,
},
};
}
// getStaticProps를 통해 구현된 컴포넌트는 빌드 시 실행되어 HTML화 된다.
export default Blog;
getServerSideProps( )
와 사용 방법은 매우 유사하나 렌더링 시점에 차이가 있다.getServerSideProps( )
: 매 요청마다 HTML 생성getStaticProps( )
: 매 빌드 시점마다 pre-renderinggetStaticProps( )
로 블로그 글 리스트를 가져오면 처음 페이지가 빌드되었을 때의 props를 그대로 유지하므로 이처럼 새로운 데이터가 반영되지 않는다.getStaticProps( )
는 항상 서버에서 실행되고, 클라이언트 측에서는 절대 동작하지 않는다.getStaticProps( )
에 정의된 코드는 클라이언트에서 받았을 때 이미 번들링을 통해 삭제되어 있으므로 API를 fetching하는 URL 등의 주요 정보가 노출될 걱정을 하지 않아도 된다.외부 데이터 요청을 함수 내부에서 호출하면 성능이 저하된다.
// lib/load-posts.js 로 분리
export async function loadPosts() {
const res = await fetch("https://url");
const data = await res.json();
return data;
}
// pages/blog.js
import { loadPosts } from "../lib/load-posts";
export async function getStaticProps() {
const posts = await loadPosts();
return { props: { posts } };
}
lib/
이라는 별도의 폴더에 파일을 생성하여 API 경로를 분리시켜주어야 한다.getStaticProps( )
의 문제점getStaticProps( )
는 빌드 시에만 렌더링할 데이터에 변화를 줄 수 있다.📢 이를 해결하기 위해 등장한 ISR!
getStaticProps( )
, revalidate
1. 사용자 1 이 해당 페이지 진입 시 설정된 시간 동안은 어느 사용자가 들어오더라도 미리 생성해두었던 페이지를 제공한다.
2. 이후 설정 시간이 지나면 Next.js 백그라운드에서 해당 페이지를 업데이트한다.
3. 업데이트가 완료된 후 접속한 사용자에게는 새롭게 만들어진 페이지가 제공된다.
// 이 함수는 Server-Side에서 빌드하는 시점에 호출된다.
// 그리고 revalidation이 활성화되어 있고, 새로운 요청이 들어온 경우 다시 호출된다.
// 즉, 첫 요청 후 10초 뒤 "새로운 요청이 있으면" 정적 생성을 진행한다.
export async function getStaticProps() {
const Next.js 12에서의 ISRres = await fetch("https://url");
const posts = await res.json();
return {
props: {
posts,
},
// 이럴 때 Next.js는 페이지를 새로 만든다!
// : 새로운 요청이 들어왔을 때 + revalidate 시간으로 설정된 10초마다
revalidate: 10,
};
}
getStaticProps( )
함수를 사용함과 동시에 return 객체에 revalidate
옵션을 주고 있다.getStaticProps( )
방식을 사용하여 빌드 타임에 렌더링을 미리 실행하되, 이후에 요청이 들어온다면 적어도 10초마다 리렌더링을 하게 된다.revalidate
과정을 필요에 따라 진행할 수 있게 해준다.revalidate
도 가능하다.// 임의의 사용자가 revalidate를 강제할 수 없도록 토큰을 생성해 사용한다.
export default async function handler(req, res) {
// 유효한 요청인지 확인한다.
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: "Invalid token" });
}
try {
// API 핸들러 내부에서 res.revalidate()를 사용해 원하는 상황에서 revalidate를 실행시킨다.
// 재작성된 경로가 아닌 실제 경로여야 한다.
// 예: "/blog/[slug]"의 경우 "/blog/post-1"로 작성해야 한다.
await res.revalidate("/path-to-revalidate");
return res.json({ revalidated: true });
} catch (err) {
// 에러가 발생하면 Next.js는 이전에 성공적으로 생성된 페이지를 계속 보여주게 된다.
return res.status(500).send("Error revalidating");
}
}
revalidate
도 가능하다.https://<your-site.com>/api/revalidate?secret=<token>
revalidate
시킬 수 있다.revalidate
해줄 수 있다.👩🏫 Next.js 13의 Data Fetching 방식에 영향을 미친 React 18 버전의 새로운 개념을 먼저 알아봅시다.
🧐 어떤 점이 좋은가요?
👩🏫 그러면 Next.js 13은 이를 사용하여 어떻게 Data Fetching 방식을 바꿨을까요?
async/await과 함께 사용되는
fetch()
API 사용
getStaticProps
, getServerSideProps
메서드는 해당 폴더에서 사용하지 못한다.fetch()
를 확장하여 cache
, next
옵션을 적용할 수 있게 하였다.// app/posts/page.js
const fetchPosts = async () => {
const response = await fetch("https://url", {
cache: "no-store",
});
return response.json();
};
const Posts = async () => {
const data = await fetchPosts();
return data.map((item, index) => <div key={item.id}>{item?.title}</div>);
};
export default Posts;
fetchPosts
)을 직접 정의하고 해당 함수로 데이터를 불러올 수 있다.cache: "no-store"
// app/posts/page.js
const fetchPosts = async () => {
const response = await fetch("https://url");
// {cache: "force-cache"} > default 값이어서 생략 가능
return response.json();
};
const Posts = async () => {
const data = await fetchPosts();
return data.map((item, index) => <div key={item.id}>{item?.title}</div>);
};
export default Posts;
cache: "force-cache"
const fetchPosts = async () => {
const response = await fetch("https://url", {
next: {
revalidate: 10,
},
});
return response.json();
};
{next: {revalidate: minute}}
옵션에 데이터를 새로 fetch 할 시간 간격을 설정해주면 된다.
정리를 정말 잘하신 것 같아요. :)