✅ 기본 React 방식
export default function HomePage() {
const [loadedMeetups, setLoadedMeetups] = useState([]);
useEffect(() => {
// http request
setLoadedMeetups(props.meetups);
}, []);
return <MeetupList meetups={loadedMeetups} />;
}
✅ NextJS 방식
📎 사전 생성
: 콘텐츠를 구성하는 모든 HTML 코드와 모든 데이터를 사전에 준비시켜 놓는다는 뜻
export async function getStaticProps(props){...}
export default function HomePage(props) {
/* const [loadedMeetups, setLoadedMeetups] = useState([]);
useEffect(() => {
// http request
setLoadedMeetups(props.meetups);
}, []); */
return <MeetupList meetups={props.meetups} />;
}
export async function getStaticProps() {
// fetch data from an API
return {
props: {
meetups: DUMMY_MEETUPS,
},
};
}
👾 파일시스템을 이용한 데이터 작업
import path from "path"; // 이 모듈은 경로를 구축하는데 유용한 기능들이 있다.
/*
📎 파일 시스템
- import fs from 'fs'; // Node.js로부터 파일시스템 모듈을 임포트한다.
- 이 패키지는 설치가 필요한 서드파티 패키지가 아닌 Node.js의 핵심 모듈이다.
- 브라우저 측 javascript가 파일시스템에 접근할 수 없기때문에 클라이언트 사이드(페이지 컴포넌트)에서는 fs 모듈 작업이 안된다.
*/
import fs from "fs/promises"; // Node.js로부터 파일시스템 모듈을 임포트한다. promise를 반환한다.
// 2️⃣ 페이지 컴포넌트 함수를 두번째에 실행한다.
export default function HomePage(props) {
const { products } = props;
return (
<ul>
{products.map((product) => (
<li key={propduct.id}>{propduct.title}</li>
))}
</ul>
);
}
// 1️⃣ getStaticProps가 있다면 이 함수를 첫번째 실행하고
export async function getStaticProps(context) {
// 여기에 작성하는 코드는 클라이언트 사이드에 절대 보이지 않는 코드로 데이터를 페칭한다.
/*
📎 fs
- readFileSync(): 파일을 동기적으로 읽고 완료될때까지 실행을 차단한다.
- readFile(path): 계속 실행하려면 콜백을 해야한다.
📎 path
: path.join에 어디서 시작하는지 알려주고 다른 Node.js객체로 현재 작업 디렉토리를 이동할 수 있다.
- join(디렉토리, data가 있는 폴더명, 사용하려는 파일 이름)
- process 객체: Node.js에서 전역적으로 사용할 수 있는 객체이다.
- cwd(): 현재 작업 디렉토리를 뜻함.
*/
// NectJS는 파일이 루트 프로젝트 폴더에 있는 것처럼 취급한다.
// 현재 작업 디렉토리가 pages폴더가 아닌 모든 전체 프로젝트 폴더가 된다.
// process.cwd()는 이 전체 프로젝트 디렉토리에서 시작한다고 알려준다.
const filePath = path.join(process.cwd(), "data", "dummy_backend.json"); // 절대 경로 구축
const jsonData = await fs.readFile(filePath);
const data = JSON.parse(jsonData); // 일반적인 js객체로 변환
return {
props: {
// 페이지 컴포넌트의 props로 해당 데이터를 전달한다.
products: data.products,
},
};
}
revalidate 프러퍼티
를 추가한다. revalidate는 점진적 정적 생성 기능을 수행한다.export default function HomePage(props) {
return <MeetupList meetups={props.meetups} />;
}
export async function getStaticProps() {
// fetch data from an API
return {
props: {
meetups: DUMMY_MEETUPS,
},
revalidate: 10, // 10초마다 페이지 재 생성
};
}
export async function getStaticProps(context) {
...
if(!data){
// 아예 DB에 엑세스할 수 없을 경우
return {
redirect: {
destination: 'no-data',
}
}
}
if (data.products.length === 0) {
return { notFound: true };
}
return {
props: {...},
revalidate: 10,
// notFound: true
}
}
notFound : true | false
redirect : { destination: 'path'}
import fs from "fs";
import path from "path";
export default function ProductDetailPage(props) {
const { loadedProduct } = props;
return (
<>
<h1>{loadedProduct.title}</h1>
<p>{loadedProduct.description}</p>
</>
);
}
export async function getStaticProps(context) {
const { params } = context;
const productId = params.pid;
const filePath = path.join(process.cwd(), "data", "dummy_backend.json"); // 현재 작업 디렉토리 / 폴더 / 데이터파일
const jsonData = await fs.readFile(filePath);
const data = JSON.parse(jsonData);
const product = data.products.find((product) => product.id === productId);
console.log(product);
return {
props: {
loadedProduct: product, // product
},
};
}
context
: 경로상의 동적 세그먼트에 대한 구체적인 값을 알수 있다.params
: 키-값 쌍의 객체🚨 error 발생
🔔 동적 페이지에서 NextJS는 어떤 [id]값이 사용 가능한지 어떤 동적 세그먼트 값을 사용할 수 있는지 알아야한다.
export async function getStaticPaths() {
// NextJS가 각 ID에 대해 getStaticProps()를 세번 호출한다.
return {
paths: [
{ params: { pid: "p1" } },
{ params: { pid: "p2" } },
{ params: { pid: "p3" } },
],
fallback: false,
};
}
paths
가 키고 인스턴스의 path의 데이터 객체를 가지는 배열값을 갖는다. (필수)params: { [id] : 'id값' }
fallback: true | false
fallback: true
는 일부 페이지만 사전 렌더링할 수 있다.fallback: true
는 파일에서 찾을 수 없는 ID에 대해서도 페이지를 렌더링할 수 있다. 동적 세그먼트의 모든 값에 대한 페이지를 일일이 다 사전 생성할 필요가 없다!👾 p1페이지만 사전 렌더링하는 예시
export async function getStaticPaths() {
return {
paths: [{ params: { pid: "p1" } }],
fallback: true,
};
}
🚨 문제
✅ 해결
export default function ProductDetailPage(props) {
const { loadedProduct } = props;
if (!loadedProduct) {
// 사전 생성할 부분이 존재하는지 확인하고
// 존재하지 않는다면 Loading과 같은 내용(폴백 컨텐츠)을 반환한다.
return <p>Loadig...</p>;
}
return (
<>
<h1>{loadedProduct.title}</h1>
<p>{loadedProduct.description}</p>
</>
);
}
...
export async function getStaticProps(context) {
const { params } = context;
const productId = params.pid;
const data = await getData();
const product = data.products.find((product) => product.id === productId);
return {
props: {
loadedProduct: product, // product
},
};
}
{ notFound: true }
: 반환한다.export async function getStaticProps(context) {
const { params } = context;
const productId = params.pid;
const data = await getData();
const product = data.products.find((product) => product.id === productId);
if (!product) {
// id에 대한 페이지가 없을 경우
return { notFound: true };
}
return {
props: {
loadedProduct: product, // product
},
};
}
getStaticProps와 getStaticPaths는 일반적으로 프로젝트를 구축할 때 호출하기 때문에 내부에서 들어오는 실제 유입되는 실제 요청에 접근할 방법이 없다.
SSR은 유입되는 모든 요청에 대한 페이지를 사전 렌더링한다. 그래서 유입되는 모든 요청에 대해서나 서버에 도달하는 특정 요청 객체에 접근할 필요가 있다. 예를들어 쿠키를 추출하는 경우이다.
페이지 컴포넌트 파일을 추가할 수 있는 함수를 제공하는데, 이 함수는 페이지 요청이 서버에 도달할 때마다 실행되는 함수이다. 서버에서만 작동하는 코드이다.
애플리케이션을 배포한 후 유입되는 모든 요청에 대해서만 재실행된다.
빌드 프로세스 중에는 실행되지 않고 요청이 들어왔을때만 서버에서 실행이 된다.
요청이 들어올 때까지 페이지가 만들어지는 것을 기다려야 한다.
매초 여러 번 바뀌는 데이터를 가지고 있을 때 사용하는 것이 좋다.
export async function getServerSideProps(context) {
return {
props: {},
// notFound: true | false (op)
// redirect: { destination: 'path' } (op)
};
}
🔔 예를들어 사용자 프로필 페이지를 구현할 때, [uid].js와 같은 동적 페이지는 다른 사용자가 url을 통해 볼 수 있게되므로, 쿠키와 헤더가 든 요청 객체에 접근해서 어느 사용자가 요청을 보냈는지 알아낸다.
export async function getServerSideProps(context) {
const { params, req, res } = context; // 동적 컴포넌트라면 접근 가능!
console.log(req); // 기본 입력 메세지
console.log(res); // 응답에 대한 객체
return {...}
}
const req = context.req;
: 요청 객체, 들어오는 요청에 접근, header와 body에도 접근과 수정 가능, 추가 데이터 정보를 받을 수 있다.const res = context.res;
: 응답 객체, 응답을 리턴하지 않고 prop key로 객체를 리턴한다. 이 키가 페이지 컴포넌트 함수 prop을 저장하고 있다.export default function HomePage(props) {
return <MeetupList meetups={props.meetups} />;
}
export async function getServerSideProps(context) {
const req = context.req; // 요청 객체
const res = context.res; // 응답 객체
// fetch data from an API
return {
props: {
meetups: DUMMY_MEETUPS,
},
};
}
👉🏻 λ
: 람다 기호가 있는 페이지들은 사전 생성하지 않고 서버 측에서만 사전 렌더링됐다는 뜻이다.
👾 일반 React에서 api호출
export default function LastSalesPage() {
const [value, setValue] = useState();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
// api 호출
setValue(data);
setIsLoading(true);
}, []);
if (isLoading) {
return <p>Loading....</p>;
}
if (!data) {
// 아직 data가 정의되어있지 않을 경우 반환!
return <p>No date yet</p>;
}
return (
<ul>
{value.map((v) => (
<li key={sale.id}>
{sale.username} - ${sale.volume}
</li>
))}
</ul>
);
}
👉🏻 React에서 useEffect는 함수가 모든 컴포넌트의 최초 평가와 렌더링을 마친 뒤 실행하도록 설계되어 있어, 첫 렌더링 사이클에서는 sales가 정의되지 않아 isLoading가 false이므로 정의되지 않은 대상에 대해 map을 호출하여 error가 발생한 것이다.
👉🏻 data와 관련된 if문을 생성하여 error를 해결한다.
✅ 결론