Next.js 컴포넌트는 종류가 2가지.
default 는 서버 컴포넌트.
파일의 최상단에 ‘use client’ 키워드를 입력하면 클라이언트 컴포넌트가 된다.
이름과 같이 ‘서버에서 렌더링되는가’, ‘클라이언트에서 렌더링 되는가’의 차이라고 볼 수 있다.
위의 문서를 보면 조금 더 명확하게 구분할 수 있다.
서버 컴포넌트는 서버 인프라를 조금 더 활용할 수 있게 해주는 역할을 한다고 할 수 있겠다.
초기 페이지 로드가 빨라지고 클라이언트 측 자바스크립트의 번들 크기가 헌저하게 줄어든다.
단, 서버 컴포넌트는 서버에서 동작하기 때문에 브라우저 API를 사용할 수 없다.
onClick 부터 useState, useEffect 까지 브라우저에서 동작하는 함수나 상태변경에 관여할 수 없다는 뜻.
요약하자면 클라이언트로 보내는 자바스크립트를 줄여 초기 페이지 로드를 줄이는 역할을 한다고 할 수 있겠다.
클라이언트 컴포넌트는 서버 컴포넌트와 반대되는 역할을 한다.
브라우저와 상호작용하는 요소를 추가하는 역할. 리액트 처럼 개발할 수 있다.
useState, useEffect 등의 훅이나 브라우저 API 를 자유롭게 사용할 수 있다.
단, 클라이언트 컴포넌트를 로드하려면 hydration 이라는 과정을 거쳐야 하기 때문에 자바스크립트 번들 크기가 커지고,
따라서 페이지 로딩 속도도 그만큼 서버 컴포넌트에 비해 느리다.
정리하자면,
큰 페이지의 틀을 잡는 것은 서버 컴포넌트로,
페이지 내부의 자잘한 기능을 추가하는 것은 클라이언트 컴포넌트로 개발하면 되겠다.
next js 는 풀스택 프레임워크를 지향한다.
따라서 서버도 구축할 수 있는데, 간편한 비관계형 데이터 베이스인 Mongo DB 를 사용한다.
Mongo DB 에서 Collection 을 생성하게 되면 아래와 같은 귀여운 모달이 뜬다.
Database 는 하나의 프로젝트를,
Collection 은 하나의 폴더를 생성한다고 생각하면 쉽다.
이전에 잠깐 Node js 와 함께 다뤄봤기 때문에 생소한 개념은 아니다.
그리고 아래와 같은 코드를 사용한다.
// db.js
import { MongoClient } from "mongodb";
const url =
"mongodb+srv://<Username>:<Password>@<ClusterName>.5xkuu8k.mongodb.net/?retryWrites=true&w=majority";
const options = { useNewUrlParser: true };
let connectDB;
if (process.env.NODE_ENV === "development") {
if (!global._mongo) {
global._mongo = new MongoClient(url, options).connect();
}
connectDB = global._mongo;
} else {
connectDB = new MongoClient(url, options).connect();
}
export { connectDB };
Mongo DB 데이터베이스에 연결하기 위한 코드.
다른 DB를 사용하더라도 비슷하게 적용된다.
const url
은 Mongo DB 클러스터에 연결하기 위한 url 이며
const option
은 MongoClient 에 전달될 옵션을 정의한다.
추가적으로 process.env.NODE_ENV 환경변수로 개발환경인지 체크해서 새로운 인스턴스를 생성할 지
하지 않을지 조건문으로 판단하는 로직. 그리고 export 해서 클러스터를 어디서든 사용할 수 있도록 한다.
서버를 가져다 쓰는 로직은 여러번 connect 되면 서버에 과부화를 주므로 한번만 실행할 수 있도록 해준 조치.
그리고 서버 컴포넌트에서 아래와 같이 사용할 수 있다.
import { connectDB } from "@/util/db";
export default async function Home() {
const client = await connectDB;
const db = client.db(<CollectionName>);
const result = await db.collection("post").find().toArray();
console.log(result)
return(<></>)
}
import 해온 connectDB 함수를 실행해서 find 메소드로 해당 DB의 모든 데이터를 가져와
toArray 메소드로 배열로 변환시킨 모습.
Next js 의 서버 컴포넌트에서 콘솔을 찍으면 신기하게 터미널에 찍힌다.
const result = await db.collection("post").find().toArray();
이 코드를 보면 find 메소드에 toArray 메소드를 사용해 배열로 만든다고 설명되어 있다.
그런데 toArray 메소드를 제거하고 find 메소드만 적용된 코드를 콘솔에 찍어보면
FindCursor {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
[Symbol(kCapture)]: false,
[Symbol(client)]: <ref *1> MongoClient {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
mongoLogger: MongoLogger {
error: [Function: bound log],
warn: [Function: bound log],
...
FindCursor 라는 객체가 출력된다. 이걸 배열로 만든다고 해서 데이터의 형태가 나올 것 같지는 않은데 ?
find 메소드는 데이터베이스에서 document 를 순차적으로 조회하는 포인터 역할을 한다.
데이터 베이스 쿼리의 결과를 참조하는 객체라는 뜻.
커서 객체의 내장 프로토타입 메소드 중 toArray 를 사용하면 해당 커서 객체의 document 를 배열로 변환한다.
데이터베이스에서 조회한 document 를 실제로 데이터로 바꿔주는 역할을 한다고 생각하면 쉬울 것 같다.
생각해보니까 데이터를 그대로 가져오는 것 보다 이렇게 참조 형식으로 조회할 수 있게 만드는게 자연스럽다.
메모리도 절약할 수 있을 것 같고 커서 객체의 다른 메소드들을 이용해 유연하게 데이터를 처리할 수 있을 것 같다.
음 그랬군.
import { connectDB } from "@/util/db";
export default async function List() {
const client = await connectDB;
const db = client.db("forum");
const result = await db.collection("post").find().toArray();
return (
<div className="list-bg">
{result.map((item) => (
<div className="list-item" key={item._id}>
<h4>{item.title}</h4>
<p>{item.date}</p>
</div>
))}
</div>
);
}
forum db의 post 컬렉션에서 데이터를 스윽 빼서 바인딩하는 모습.
아주 간단하기 이를 데 없다.