레파지토리를 Vercel에 연결해서 배포하기
- Vercel에 새로운 프로젝트를 생성해서 Github 레파지토리 연결 후 배포한다.
DB 생성하기
- Vercel
Storage
탭에서 Postgres database를 생성한다.
- 이때
Region
을 내 위치와 가까운 국가로 설정하면 데이터 호출 시 latency를 줄일 수 있다. (Settings
탭에서 Functions > Function Region까지 설정하는 것을 추천한다.)
- Postgres database 관련 코드를 루트 디렉토리의
.env
파일에 추가한다.
DB에 초기 데이터 넣기
"scripts": {
"build": "next build",
"dev": "next dev",
"start": "next start",
"seed": "node -r dotenv/config ./scripts/seed.js"
},
package.json
파일에 seed
명령어를 추가한다.
npm run seed
를 실행하면 SQL를 이용해 테이블들을 생성하고 placeholder-data.js
의 데이터를 이 테이블들에 초기 데이터로 채워넣는다.
데이터를 불러오는 방법
- 데이터를 클라이언트에서 불러온다면 DB 관련 민감한 코드가 노출되는 것을 막기 위해 서버에서 실행되어 애플리케이션과 DB 사이를 중계하는
API layer
를 사용하는 것이 좋다.
- Postgres와 같은 관계형 데이터베이스의 경우 SQL이나 Prisma와 같은 ORM을 사용하여
Database queries
를 수행할 수 있다.
- 만약
React Server Components
를 사용한다면 API layer
를 사용하지 않더라도 코드가 노출될 위험 없이 DB를 직접적으로 query 할 수 있다. (Database queries)
서버 컴포넌트로 데이터를 불러오는 것의 장점
- 서버 컴포넌트는 서버에서 실행되기 때문에 값 비싼 데이터 fetching 등의 로직은 서버에 맡기고 클라이언트에는 결과만을 전송할 수 있다.
- 서버 컴포넌트는
promises
를 지원하기 때문에 비동기적으로 데이터를 fetching 할 수 있다. useEffect
, useState
, 혹은 다른 라이브러리를 사용하지 않고도 async/await
문법을 사용할 수 있다.
- 서버 컴포넌트는 서버에서 실행되기 때문에 추가적인
API layer
없이 직접적으로 DB를 query 할 수 있다.
Dashboard 페이지에 데이터 불러오기
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import { fetchRevenue } from '../lib/data';
export default async function Page() {
const revenue = await fetchRevenue();
return (
<RevenueChart revenue={revenue} />
);
}
Page
는 async
컴포넌트이기 때문에 await
를 이용해서 데이터를 불러올 수 있다.
- 하지만 현재 코드에서는 데이터 요청들이 의도치 않게 서로를 막고 있기 때문에
request waterfall
이 발생한다.
request waterfalls란?
waterfall
은 이전 요청의 완료에 따라 결정되는 일련의 네트워크 요청을 의미한다
- 데이터를 불러오는 경우 이전의 리퀘스트가 데이터를 return 했을 때만 다음 리퀘스트를 시작할 수 있다.
const revenue = await fetchRevenue();
const latestInvoices = await fetchLatestInvoices();
- 예를 들어 상단의 코드에서
fetchRevenue()
가 완료될 때까지 fetchLatestInvoices()
를 실행할 수 없다.
병렬적으로 데이터 불러오기
export async function fetchCardData() {
try {
const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices`;
const customerCountPromise = sql`SELECT COUNT(*) FROM customers`;
const invoiceStatusPromise = sql`SELECT
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid",
SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS "pending"
FROM invoices`;
const data = await Promise.all([
invoiceCountPromise,
customerCountPromise,
invoiceStatusPromise,
]);
}
}
waterfalls
를 방지할 수 있는 가장 흔한 방법은 모든 데이터를 병렬적으로, 즉 동시에 요청하는 것이다.
- 자바스크립트에서는 모든
promises
를 동시에 시작하기 위해 Promise.all()
혹은 Promise.allSettled()
를 사용할 수 있다.
- 모든 데이터를 동시에 불러옴으로써 성능을 향상시킬 수 있다.
Static Rendering이란?
Static Rendering
을 사용하면 빌드 타임(배포 시) 혹은 revalidation
하는 동안 서버에서 데이터를 fetching하고 렌더링한다.
- 결과물은 CDN(Content Delivery Network)에 분배되고 캐싱된다.
Static Rendering
은 블로그 글, 제품 페이지처럼 모든 사용자에게 공유되는 데이터를 제공할 때 유용하지만, 데이터가 정기적으로 업데이트되는 대시보드에는 적합하지 않다.
- Next.js에서는 default 렌더링 방식으로
Static Rendering
을 사용한다.
Static Rendering의 장점
- 웹사이트 속도 향상: 미리 렌더링된 콘텐츠는 캐싱되기 때문에 전세계의 사용자들은 웹 사이트의 콘텐츠에 보다 빠르고 안정적으로 접근할 수 있다.
- 서버 부담 감소: 콘텐츠가 캐싱되기 때문에 서버는 모든 사용자 요청에 콘텐츠를 동적으로 생성할 필요가 없다.
- SEO: 미리 렌더링된 콘텐츠는 페이지가 로드될 때 이미 사용 가능하기 때문에 검색 엔진 크롤러가 색인하기 더 쉽다.
Dynamic Rendering이란?
Dynamic Rendering
을 사용하면 사용자가 요청할 때마다(사용자가 페이지에 방문할 때마다) 콘텐츠가 서버에서 렌더링된다.
Dynamic Rendering
은 데이터가 자주 업데이트되는 애플리케이션에 적합하다.
import { unstable_noStore as noStore } from 'next/cache';
export async function fetchRevenue() {
noStore();
}
- data fetching 시에
{cache: 'no-store'}
를 설정하면 모든 리퀘스트마다 데이터를 refetch하는 방식으로 Dynamic Rendering
를 사용할 수 있다.
Dynamic Rendering의 장점
- 실시간 데이터:
Dynamic rendering
을 사용하면 실시간 혹은 자주 업데이트되는 데이터를 제공할 수 있다.
- 사용자별 데이터 제공: 데이터가 사용자의 상호작용에 따라 업데이트되기 때문에 개인별 대시보드나 사용자 프로필 정보와 같이 사용자별로 데이터를 제공하기에 좋다.
- 리퀘스트 시 정보 제공: 쿠키나 URL
search params
와 같이 리퀘스트 시에만 알 수 있는 정보에 접근할 수 있다.